akiyosi.goneovim/editor/window.go
2025-04-17 21:53:17 +09:00

4145 lines
95 KiB
Go

package editor
import (
"bytes"
"errors"
"fmt"
"math"
"runtime"
"sort"
"strings"
"sync"
"time"
"unsafe"
"github.com/akiyosi/goneovim/util"
"github.com/akiyosi/qt/core"
"github.com/akiyosi/qt/gui"
"github.com/akiyosi/qt/widgets"
"github.com/bluele/gcache"
"github.com/neovim/go-client/nvim"
)
const (
EXTWINBORDERSIZE = 5
EXTWINMARGINSIZE = 10
)
type gridId = int
// Highlight is
type Highlight struct {
special *RGBA
foreground *RGBA
background *RGBA
kind string
uiName string
hlName string
// altfont string
blend int
id int
reverse bool
bold bool
underline bool
undercurl bool
italic bool
strikethrough bool
underdouble bool
underdotted bool
underdashed bool
}
// HlText is used in screen cache
type HlTextKey struct {
fg *RGBA
text string
italic bool
bold bool
}
// HlDecorationKey is used in screen cache
type HlDecorationKey struct {
fg *RGBA
bg *RGBA
sp *RGBA
underline bool
undercurl bool
strikethrough bool
underdouble bool
underdotted bool
underdashed bool
}
type HlBgKey struct {
bg *RGBA
length int
}
// Cell is
type Cell struct {
highlight *Highlight
char string
normalWidth bool
covered bool
scaled bool
}
type IntInt [2]int
// ExternalWin is
type ExternalWin struct {
widgets.QDialog
}
type inputMouseEvent struct {
button string
action string
mod string
grid gridId
row int
col int
event *gui.QMouseEvent
}
type zindex struct {
value int
order int
nearestLowerZOrderWindow *Window
}
// Window is
type Window struct {
cache Cache
widgets.QWidget
smoothScrollAnimation *core.QPropertyAnimation
snapshot *gui.QPixmap
imagePainter *gui.QPainter
font *Font
fallbackfonts []*Font
localWindows *[4]localWindow
extwin *ExternalWin
background *RGBA
s *Screen
anchorwin *Window
anchor string
anchorGrid int
anchorCol int
anchorRow int
ft string
lenOldContent []int
lenContent []int
lenLine []int
scrollRegion []int
contentMaskOld [][]bool
contentMask [][]bool
content [][]*Cell
extwinAutoLayoutPosY []int
extwinAutoLayoutPosX []int
charsScaledLineHeight []string
scrollViewport [6]int
queueRedrawArea [4]int
extwinRelativePos [2]int
pos [2]int
scrollPixels [2]int
viewportMargins [4]int
wb int
height int
grid gridId
width float64
_ float64 `property:"scrollDiff"`
lastMouseEvent *inputMouseEvent
cols int
maxLenContent int
ts int
devicePixelRatio float64
scrollPixels2 int
scrollPixels3 int
id nvim.Window
scrollDelta float64
rows int
zindex *zindex
lastScrollphase core.Qt__ScrollPhase
updateMutex sync.RWMutex
paintMutex sync.RWMutex
propMutex sync.RWMutex
redrawMutex sync.Mutex
extwinConnectResizable bool
extwinResized bool
extwinManualResized bool
doErase bool
isPopupmenu bool
isExternal bool
isFloatWin bool
isMsgGrid bool
isGridDirty bool
}
type localWindow struct {
grid gridId
isResized bool
localWidth float64
localHeight int
}
func purgeQimage(key, value interface{}) {
image := value.(*gui.QImage)
image.DestroyQImage()
}
func newCache() Cache {
g := gcache.New(editor.config.Editor.CacheSize).LRU().
EvictedFunc(purgeQimage).
PurgeVisitorFunc(purgeQimage).
Build()
return *(*Cache)(unsafe.Pointer(&g))
}
func (c *Cache) set(key, value interface{}) error {
return c.Set(key, value)
}
func (c *Cache) get(key interface{}) (interface{}, error) {
return c.Get(key)
}
func (c *Cache) purge() {
c.Purge()
}
func (w *Window) dropScreenSnapshot() {
if w.snapshot == nil {
return
}
w.paintMutex.Lock()
w.snapshot.DestroyQPixmap()
w.snapshot = nil
w.paintMutex.Unlock()
}
func (w *Window) grabScreenSnapshot() {
snapshot := w.grabScreen()
w.paintMutex.Lock()
w.snapshot.DestroyQPixmap()
w.snapshot = snapshot
w.paintMutex.Unlock()
}
func (w *Window) grabScreen() *gui.QPixmap {
var rect *core.QRect
fullRect := w.Rect()
font := w.getFont()
rect = core.NewQRect4(
fullRect.X()+w.viewportMargins[2]*int(font.cellwidth),
fullRect.Y()+(w.viewportMargins[0]*font.lineHeight),
fullRect.Width()-w.viewportMargins[2]*int(font.cellwidth)-w.viewportMargins[3]*int(font.cellwidth),
fullRect.Height()-(w.viewportMargins[0]*font.lineHeight)-(w.viewportMargins[1]*font.lineHeight),
)
return w.Grab(rect)
}
func (w *Window) paint(event *gui.QPaintEvent) {
editor.putLog(
fmt.Sprintf("paint start"),
)
w.paintMutex.Lock()
p := gui.NewQPainter2(w)
// clip rect
rect := event.Rect()
// p.SetClipRect2(rect, core.Qt__ReplaceClip)
// Erase the snapshot used in the animation scroll
if w.doErase {
p.EraseRect3(w.Rect())
p.DestroyQPainter()
w.paintMutex.Unlock()
return
}
// Set RenderHint
p.SetRenderHint(gui.QPainter__SmoothPixmapTransform, true)
// Set font
font := w.getFont()
// Set devicePixelRatio if it is not set
devicePixelRatio := float64(p.PaintEngine().PaintDevice().DevicePixelRatio())
if w.devicePixelRatio != devicePixelRatio {
if w.devicePixelRatio != 0 {
w.s.purgeTextCacheForWins()
}
w.devicePixelRatio = devicePixelRatio
}
col := int(math.Trunc(float64(rect.Left()) / font.cellwidth))
row := int(math.Trunc(float64(rect.Top()) / float64(font.lineHeight)))
cols := int(math.Ceil(float64(rect.Width()) / font.cellwidth))
if rect.Width()%int(math.Trunc(font.cellwidth)) > 0 || rect.Left()%int(math.Trunc(font.cellwidth)) > 0 {
cols++
}
rows := int(math.Ceil(float64(rect.Height()) / float64(font.lineHeight)))
if rect.Height()%font.lineHeight > 0 || rect.Top()%font.lineHeight > 0 {
rows++
}
var verScrollPixels int
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
// draw default background color if window is float window or msg grid
isDrawDefaultBg := false
if editor.config.Editor.EnableBackgroundBlur ||
editor.config.Editor.Transparent < 1.0 {
if !w.isExternal {
if w.isFloatWin {
isDrawDefaultBg = true
}
}
} else {
if w.isMsgGrid && editor.config.Message.Transparent < 1.0 {
isDrawDefaultBg = true
} else if w.isPopupmenu && w.s.ws.pb > 0 {
isDrawDefaultBg = true
} else if w.isFloatWin && w.wb > 0 {
isDrawDefaultBg = true
}
}
// In transparent mode and float windows, there is no need to automatically draw the
// background color of the entire grid, so the background color is not automatically drawn.
if isDrawDefaultBg {
w.SetAutoFillBackground(false)
}
// -------------
// Draw contents
// -------------
if verScrollPixels <= 0 {
for y := row + rows; y >= row; y-- {
if y < w.viewportMargins[0] {
continue
}
if y > w.rows-w.viewportMargins[1]-1 {
continue
}
w.drawBackground(p, y, col, cols, isDrawDefaultBg)
w.drawForeground(p, y, col, cols)
}
} else {
for y := row; y <= row+rows; y++ {
if y < w.viewportMargins[0] {
continue
}
if y > w.rows-w.viewportMargins[1]-1 {
continue
}
w.drawBackground(p, y, col, cols, isDrawDefaultBg)
w.drawForeground(p, y, col, cols)
}
}
// Draw scroll snapshot
w.drawScrollSnapshot(p)
// // Draw content outside the viewportMargin in the y-axis direction
for y := row; y <= row+rows; y++ {
if y >= w.viewportMargins[0] {
continue
}
w.drawBackground(p, y, col, cols, isDrawDefaultBg)
w.drawForeground(p, y, col, cols)
}
for y := row + rows; y >= row; y-- {
if y <= w.rows-w.viewportMargins[1]-1 {
continue
}
w.drawBackground(p, y, col, cols, isDrawDefaultBg)
w.drawForeground(p, y, col, cols)
}
// TODO: We should use msgSepChar to separate message window area
// // If Window is Message Area, draw separator
// if w.isMsgGrid {
// w.drawMsgSeparator(p)
// }
// Draw indent guide
if editor.config.Editor.IndentGuide {
w.drawIndentguide(p, row, rows)
}
// Draw float window border
if editor.config.Editor.DrawBorderForFloatWindow {
w.drawFloatWindowBorder(p)
}
// Draw vim window separator
if editor.config.Editor.DrawWindowSeparator {
w.drawWindowSeparators(p, row, col, rows, cols)
}
// Minimap drawing process. It is not involved in the normal drawing of the window at all.
if w.s.name == "minimap" {
if w.s.ws.minimap != nil {
if w.s.ws.minimap.visible && w.s.ws.minimap.widget.IsVisible() {
w.s.ws.minimap.updateCurrentRegion(p)
}
}
}
w.adjustSmoothScrollAmount()
p.DestroyQPainter()
w.paintMutex.Unlock()
}
func (w *Window) adjustSmoothScrollAmount() {
// Reset to 0 after drawing is complete.
// This is to suppress flickering in smooth scroll
font := w.getFont()
horizontalScrollAmount := font.cellwidth
verticalScrollAmount := float64(font.lineHeight)
dx := math.Abs(float64(w.scrollPixels[0]))
dy := math.Abs(float64(w.scrollPixels[1]))
if dx >= horizontalScrollAmount {
w.scrollPixels[0] = 0
}
if dy >= verticalScrollAmount {
w.scrollPixels[1] = 0
}
if w.lastScrollphase == core.Qt__NoScrollPhase {
w.lastScrollphase = core.Qt__ScrollEnd
}
}
func (w *Window) drawScrollSnapshot(p *gui.QPainter) {
if !editor.config.Editor.SmoothScroll {
return
}
if w.s.name == "minimap" {
return
}
if w.snapshot == nil {
return
}
if editor.isKeyAutoRepeating {
return
}
if w.scrollPixels2 == 0 {
return
}
font := w.getFont()
height := math.Abs(w.scrollDelta) * float64(font.lineHeight)
var snapshotPosX, snapshotPosY float64
snapshotPosX = float64(w.viewportMargins[2]) * font.cellwidth
if w.scrollPixels2 > 0 {
snapshotPosY = float64(w.scrollPixels2) - height
} else if w.scrollPixels2 < 0 {
snapshotPosY = (float64(w.snapshot.Height()) / w.devicePixelRatio) + float64(w.scrollPixels2)
}
snapshotPosY += float64(w.viewportMargins[0] * font.lineHeight)
var drawPos *core.QPointF
var sourceRect *core.QRectF
if w.scrollPixels2 > 0 {
drawPos = core.NewQPointF3(
snapshotPosX,
snapshotPosY,
)
sourceRect = core.NewQRectF4(
0,
0,
float64(w.snapshot.Width()),
math.Abs(height)*w.devicePixelRatio,
)
} else if w.scrollPixels2 < 0 {
drawPos = core.NewQPointF3(
snapshotPosX,
snapshotPosY,
)
sourceRect = core.NewQRectF4(
0,
(float64(w.snapshot.Height())/w.devicePixelRatio-math.Abs(height))*w.devicePixelRatio,
float64(w.snapshot.Width()),
math.Abs(height)*w.devicePixelRatio,
)
}
if w.scrollPixels2 != 0 {
p.DrawPixmap5(
drawPos,
w.snapshot,
sourceRect,
)
}
}
func (w *Window) getFont() *Font {
if w.font == nil {
return w.s.font
}
return w.font
}
func (w *Window) getFallbackFonts() []*Font {
if w.font == nil {
return w.s.fallbackfonts
}
return w.fallbackfonts
}
func (w *Window) drawIndentguide(p *gui.QPainter, row, rows int) {
if w == nil {
return
}
if w.grid == 1 || w.isMsgGrid {
return
}
if w.s.name == "minimap" {
return
}
if w.isMsgGrid {
return
}
if w.ft == "" {
return
}
for _, v := range editor.config.Editor.IndentGuideIgnoreFtList {
if v == w.ft {
return
}
}
if !w.isShown() {
return
}
if w.ts == 0 {
return
}
ts := w.ts
headspaceOfRows := make(map[int]int)
for y := row; y < rows; y++ {
if y+1 >= len(w.content) {
break
}
l, _ := w.countHeadSpaceOfLine(y)
headspaceOfRows[y] = l
}
drawIndents := make(map[IntInt]bool)
for y := row; y < rows; y++ {
if y+1 >= len(w.content) {
break
}
// nextline := w.content[y+1]
line := w.content[y]
res := 0
for x := 0; x < w.maxLenContent; x++ {
if x+1 >= len(line) {
break
}
if line[x+1] == nil {
continue
}
c := line[x]
if c == nil {
continue
}
if c.highlight.isSignColumn() {
res++
}
if c.char != " " && !c.highlight.isSignColumn() {
break
}
// yylen, _ := w.countHeadSpaceOfLine(y)
yylen := headspaceOfRows[y]
if x > res && (x+1-res)%ts == 0 {
ylen := x + 1
if ylen > yylen {
break
}
doPaintIndent := false
for mm := y; mm < len(w.content); mm++ {
if drawIndents[[2]int{x + 1, mm}] {
continue
}
// mmlen, _ := w.countHeadSpaceOfLine(mm)
mmlen := headspaceOfRows[mm]
if mmlen == ylen {
break
}
if mmlen > ylen && w.lenLine[mm] > res {
doPaintIndent = true
}
if mmlen == w.cols && !doPaintIndent {
for nn := mm + 1; nn < len(w.content); nn++ {
// nnlen, _ := w.countHeadSpaceOfLine(nn)
nnlen := headspaceOfRows[nn]
if nnlen == ylen {
break
}
if nnlen < ylen {
break
}
if nnlen > ylen && w.lenLine[nn] > res {
doPaintIndent = true
}
}
}
if mmlen < ylen {
doBreak := true
// If the line to draw an indent-guide has a wrapped line
// in the next line, do not skip drawing
// TODO: We do not detect the wrapped line when `:set nonu` setting.
if mm+1 < len(w.content) {
// lllen, _ := w.countHeadSpaceOfLine(mm+1)
lllen := headspaceOfRows[mm+1]
if mm >= 0 {
if lllen > ylen {
for xx := 0; xx < w.lenLine[mm]; xx++ {
if xx >= len(w.content[mm]) {
continue
}
if w.content[mm][xx] == nil {
continue
}
if w.content[mm][xx].highlight.hlName == "LineNr" {
if w.content[mm][xx].char == " " {
doBreak = false
} else if w.content[mm][xx].char != " " {
doBreak = true
break
}
}
}
}
}
}
if doBreak {
break
}
}
if w.content[mm][x+1] == nil {
break
}
if w.content[mm][x+1].char != " " {
break
}
if !doPaintIndent {
break
}
if !drawIndents[[2]int{x + 1, mm}] {
drawIndents[[2]int{x + 1, mm}] = true
}
}
}
}
}
// detect current block
currentBlock := make(map[IntInt]bool)
for x := w.s.cursor[1]; x >= 0; x-- {
if drawIndents[[2]int{x + 1, w.s.cursor[0]}] {
for y := w.s.cursor[0]; y >= 0; y-- {
if drawIndents[[2]int{x + 1, y}] {
currentBlock[[2]int{x + 1, y}] = true
}
if !drawIndents[[2]int{x + 1, y}] {
break
}
}
for y := w.s.cursor[0]; y < len(w.content); y++ {
if drawIndents[[2]int{x + 1, y}] {
currentBlock[[2]int{x + 1, y}] = true
}
if !drawIndents[[2]int{x + 1, y}] {
break
}
}
break
}
}
// draw indent guide
for y := row; y < len(w.content); y++ {
for x := 0; x < w.maxLenContent; x++ {
if !drawIndents[[2]int{x + 1, y}] {
continue
}
if currentBlock[[2]int{x + 1, y}] {
w.drawIndentline(p, x+1, y, true)
} else {
w.drawIndentline(p, x+1, y, false)
}
}
}
}
func (w *Window) drawIndentline(p *gui.QPainter, x int, y int, b bool) {
font := w.getFont()
// Set smooth scroll offset
var horScrollPixels, verScrollPixels int
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
if w.s.ws.mouseScroll != "" {
horScrollPixels += w.scrollPixels[0]
}
X := float64(x)*font.cellwidth + float64(horScrollPixels)
Y := float64(y*font.lineHeight) + float64(verScrollPixels)
var color *RGBA = editor.colors.indentGuide
var lineWeight float64 = 1
if b {
color = warpColor(editor.colors.indentGuide, -40)
lineWeight = 1.5
}
p.FillRect4(
core.NewQRectF4(
X,
Y,
lineWeight,
float64(font.lineHeight),
),
color.QColor(),
)
if w.lenContent[y] < x {
w.lenContent[y] = x
}
}
func (w *Window) drawMsgSeparator(p *gui.QPainter) {
highNo, ok := w.s.highlightGroup["MsgSeparator"]
if !ok {
return
}
hl, ok := w.s.hlAttrDef[highNo]
if !ok {
return
}
if hl == nil {
return
}
color := hl.fg()
p.FillRect4(
core.NewQRectF4(
0,
0,
float64(w.Width()),
1,
),
gui.NewQColor3(
color.R,
color.G,
color.B,
200),
)
}
func (w *Window) drawFloatWindowBorder(p *gui.QPainter) {
if !w.isFloatWin {
return
}
if !w.isExternal {
return
}
var color *RGBA
highNo, ok := w.s.highlightGroup["GoneovimFloatWindowBorder"]
if !ok {
color = editor.colors.fg
} else {
hl, ok := w.s.hlAttrDef[highNo]
if !ok || hl == nil {
color = editor.colors.fg
} else {
color = hl.fg()
}
}
width := float64(w.Width())
height := float64(w.Height())
left := core.NewQRectF4(0, 0, 1, height)
top := core.NewQRectF4(0, 0, width, 1)
right := core.NewQRectF4(width-1, 0, 1, height)
bottom := core.NewQRectF4(0, height-1, width, 1)
p.FillRect4(
left,
gui.NewQColor3(
color.R,
color.G,
color.B,
128),
)
p.FillRect4(
top,
gui.NewQColor3(
color.R,
color.G,
color.B,
128),
)
p.FillRect4(
right,
gui.NewQColor3(
color.R,
color.G,
color.B,
128),
)
p.FillRect4(
bottom,
gui.NewQColor3(
color.R,
color.G,
color.B,
128),
)
}
func (w *Window) drawWindowSeparators(p *gui.QPainter, row, col, rows, cols int) {
if w == nil {
return
}
if w.grid != 1 {
return
}
gwin, ok := w.s.getWindow(1)
if !ok {
return
}
gwinrows := gwin.rows
w.s.windows.Range(func(_, winITF interface{}) bool {
win := winITF.(*Window)
if win == nil {
return true
}
if !win.isShown() {
return true
}
if win.isFloatWin {
return true
}
if win.isMsgGrid {
return true
}
if win.pos[0]+win.cols < row && (win.pos[1]+win.rows+1) < col {
return true
}
if win.pos[0] > (row+rows) && (win.pos[1]+win.rows) > (col+cols) {
return true
}
win.drawWindowSeparator(p, gwinrows)
return true
})
}
func (w *Window) drawWindowSeparator(p *gui.QPainter, gwinrows int) {
font := w.getFont()
// window position is based on cols, rows of global font setting
x := int(float64(w.pos[0]) * w.s.font.cellwidth)
y := w.pos[1] * w.s.font.lineHeight
color := editor.colors.windowSeparator
width := int(float64(w.cols) * font.cellwidth)
winHeight := int((float64(w.rows) + 0.92) * float64(font.lineHeight))
// Vim uses the showtabline option to change the display state of the tabline
// based on the number of tabs. We need to look at these states to adjust
// the length and display position of the window separator
tablineNum := 0
numOfTabs := w.s.ws.getNumOfTabs()
if numOfTabs > 1 {
tablineNum = 1
}
isDrawTabline := editor.config.Tabline.Visible && editor.config.Editor.ExtTabline
if w.s.ws.showtabline == 2 && isDrawTabline && numOfTabs == 1 {
tablineNum = -1
}
shift := font.lineHeight / 2
if w.rows+w.s.ws.showtabline+tablineNum+1 == gwinrows {
winHeight = w.rows * font.lineHeight
shift = 0
} else {
if w.pos[1] == tablineNum {
winHeight = w.rows*font.lineHeight + int(float64(font.lineHeight)/2.0)
shift = 0
}
if w.pos[1]+w.rows == gwinrows-2 {
winHeight = w.rows*font.lineHeight + int(float64(font.lineHeight)/2.0)
}
}
// Vertical
if y+font.lineHeight+1 < w.s.widget.Height() {
p.FillRect5(
int(float64(x+width)+font.cellwidth/2),
y-shift,
2,
winHeight,
color.QColor(),
)
}
// vertical gradient
if editor.config.Editor.WindowSeparatorGradient {
gradient := gui.NewQLinearGradient3(
float64(x+width)+font.cellwidth/2,
0,
float64(x+width)+font.cellwidth/2-6,
0,
)
gradient.SetColorAt(0, gui.NewQColor3(color.R, color.G, color.B, 125))
gradient.SetColorAt(1, gui.NewQColor3(color.R, color.G, color.B, 0))
brush := gui.NewQBrush10(gradient)
p.FillRect2(
int(float64(x+width)+font.cellwidth/2)-6,
y-shift,
6,
winHeight,
brush,
)
}
bottomBorderPos := w.pos[1]*w.s.font.lineHeight + w.Rect().Bottom()
isSkipDrawBottomBorder := bottomBorderPos > w.s.bottomWindowPos()-w.s.font.lineHeight && bottomBorderPos < w.s.bottomWindowPos()+w.s.font.lineHeight
if isSkipDrawBottomBorder {
return
}
// Horizontal
height := w.rows * font.lineHeight
y2 := y + height - 1 + font.lineHeight/2
p.FillRect5(
int(float64(x)-font.cellwidth/2),
y2,
int((float64(w.cols)+0.92)*font.cellwidth),
2,
color.QColor(),
)
// horizontal gradient
if editor.config.Editor.WindowSeparatorGradient {
hgradient := gui.NewQLinearGradient3(
0,
float64(y2),
0,
float64(y2)-6,
)
hgradient.SetColorAt(0, gui.NewQColor3(color.R, color.G, color.B, 125))
hgradient.SetColorAt(1, gui.NewQColor3(color.R, color.G, color.B, 0))
hbrush := gui.NewQBrush10(hgradient)
p.FillRect2(
int(float64(x)-font.cellwidth/2),
y2-6,
int((float64(w.cols)+0.92)*font.cellwidth),
6,
hbrush,
)
}
}
func (w *Window) wheelEvent(event *gui.QWheelEvent) {
if !w.s.ws.isMouseEnabled {
return
}
w.dropScreenSnapshot()
var v, h, vert, horiz int
var action string
editor.putLog("start wheel event")
font := w.getFont()
mouseScroll := w.s.ws.mouseScroll
if mouseScroll == "" {
mouseScroll = "ver:2,hor:1"
}
pixels := event.PixelDelta()
if pixels != nil {
v = pixels.Y()
h = pixels.X()
}
// faster move in darwin
if runtime.GOOS == "darwin" {
v = v * 2
h = h * 2
}
phase := event.Phase()
if phase == core.Qt__ScrollEnd {
w.scrollPixels3 = 0
}
w.lastScrollphase = phase
emitScrollEnd := (w.lastScrollphase == core.Qt__ScrollEnd)
// handle MouseScrollingUnit configuration item
// if value is "line":
doAngleScroll := false
if editor.config.Editor.MouseScrollingUnit == "line" {
doAngleScroll = true
}
// if value is "smart":
if editor.config.Editor.MouseScrollingUnit == "smart" {
if w.s.ws.mouseScrollTemp != "ver:1,hor:1" {
w.applyTemporaryMousescroll("ver:1,hor:1")
}
if math.Abs(float64(v)) > float64(font.lineHeight*2) {
doAngleScroll = true
w.applyTemporaryMousescroll(w.s.ws.mouseScroll)
} else {
doAngleScroll = false
if w.s.ws.mouseScrollTemp != "ver:1,hor:1" {
w.applyTemporaryMousescroll("ver:1,hor:1")
}
}
if emitScrollEnd {
w.applyTemporaryMousescroll(w.s.ws.mouseScroll)
}
}
// if value is "pixel":
if editor.config.Editor.MouseScrollingUnit == "pixel" {
w.applyTemporaryMousescroll("ver:1,hor:1")
if emitScrollEnd {
w.applyTemporaryMousescroll(w.s.ws.mouseScroll)
}
}
if editor.config.Editor.DisableHorizontalScroll {
h = 0
}
if (v == 0 || h == 0) && emitScrollEnd && !doAngleScroll && !w.s.ws.isTerminalMode {
vert, horiz = w.smoothUpdate(v, h, emitScrollEnd)
} else if (v != 0 || h != 0) && phase != core.Qt__NoScrollPhase && !doAngleScroll && !w.s.ws.isTerminalMode {
// If Scrolling has ended, reset the displacement of the line
vert, horiz = w.smoothUpdate(v, h, emitScrollEnd)
} else {
angles := event.AngleDelta()
vert = angles.Y()
horiz = angles.X()
// Scroll per 1 line
if vert < 0 {
vert = -1
} else if vert > 0 {
vert = 1
}
if horiz < 0 {
horiz = -1
} else if horiz > 0 {
horiz = 1
}
}
if vert == 0 && horiz == 0 && w.s.ws.mouseScroll == "" {
return
}
if vert == 0 && horiz == 0 {
return
}
if editor.config.Editor.ReversingScrollDirection {
if vert < 0 {
action = "up"
} else if vert > 0 {
action = "down"
}
} else {
if vert > 0 {
action = "up"
} else if vert < 0 {
action = "down"
}
}
mod := editor.modPrefix(event.Modifiers())
col := int(float64(event.X()) / font.cellwidth)
row := int(float64(event.Y()) / float64(font.lineHeight))
if w.s.ws.isMappingScrollKey || w.s.ws.mouseScroll != "" {
if vert != 0 {
w.s.ws.nvim.InputMouse("wheel", action, mod, w.grid, row, col)
}
} else {
verAmount := editor.config.Editor.LineToScroll * int(math.Abs(float64(vert)))
scrollUpKey := "<C-y>"
scrollDownKey := "<C-e>"
var scrollKey string
if editor.config.Editor.ReversingScrollDirection {
if vert > 0 {
scrollKey = fmt.Sprintf("%v%s", verAmount, scrollDownKey)
} else if vert < 0 {
scrollKey = fmt.Sprintf("%v%s", verAmount, scrollUpKey)
}
} else {
if vert > 0 {
scrollKey = fmt.Sprintf("%v%s", verAmount, scrollUpKey)
} else if vert < 0 {
scrollKey = fmt.Sprintf("%v%s", verAmount, scrollDownKey)
}
}
go w.s.ws.nvim.Input(scrollKey)
}
if editor.config.Editor.DisableHorizontalScroll {
return
}
if horiz > 0 {
action = "left"
} else if horiz < 0 {
action = "right"
} else {
return
}
if horiz != 0 {
go w.s.ws.nvim.InputMouse("wheel", action, mod, w.grid, row, col)
}
event.Accept()
}
func (w *Window) applyTemporaryMousescroll(ms string) {
cmd := "set mousescroll=" + ms
o := make(map[string]interface{})
o["output"] = true
outCh := make(chan map[string]interface{}, 5)
go func() {
out, _ := w.s.ws.nvim.Exec(cmd, o)
outCh <- out
}()
select {
case <-outCh:
w.s.ws.mouseScrollTemp = ms
case <-time.After(NVIMCALLTIMEOUT * time.Millisecond):
}
}
func (w *Window) isEventEmitOnCursorGrid() bool {
return w.grid == w.s.ws.cursor.gridid
}
// screen smooth update with touchpad
func (w *Window) smoothUpdate(v, h int, emitScrollEnd bool) (int, int) {
var vert, horiz int
font := w.getFont()
if emitScrollEnd {
w.scrollPixels[0] = 0
w.scrollPixels[1] = 0
w.queueRedrawAll()
w.refreshUpdateArea(1)
w.update()
w.s.ws.cursor.update()
return 0, 0
}
if h < 0 && w.scrollPixels[0] > 0 {
w.scrollPixels[0] = 0
if !emitScrollEnd {
w.scrollPixels3 = 0
}
}
if v < 0 && w.scrollPixels[1] > 0 {
w.scrollPixels[1] = 0
}
dx := math.Abs(float64(w.scrollPixels[0]))
dy := math.Abs(float64(w.scrollPixels[1]))
horizontalScrollAmount := font.cellwidth
verticalScrollAmount := float64(font.lineHeight)
if math.Abs(float64(w.scrollPixels3)) < 20 {
if math.Abs(float64(h)) > math.Abs(float64(v)) {
w.scrollPixels3 += h
}
h = 0
}
if dx < horizontalScrollAmount {
w.scrollPixels[0] += h
}
if dy < verticalScrollAmount {
w.scrollPixels[1] += v
}
dx = math.Abs(float64(w.scrollPixels[0]))
dy = math.Abs(float64(w.scrollPixels[1]))
if dx >= horizontalScrollAmount {
horiz = int(float64(w.scrollPixels[0]) / horizontalScrollAmount)
}
if dy >= verticalScrollAmount {
vert = int(float64(w.scrollPixels[1]) / verticalScrollAmount)
// NOTE: Reset to 0 after paint event is complete.
// This is to suppress flickering.
}
// w.update()
// w.s.ws.cursor.update()
if !(dx >= horizontalScrollAmount || dy > verticalScrollAmount) {
w.update()
w.s.ws.cursor.update()
}
return vert, horiz
}
// smoothscroll makes Neovim's scroll command behavior smooth and animated.
func (win *Window) smoothScroll(delta float64) {
if !editor.config.Editor.SmoothScroll {
return
}
win.initializeOrReuseSmoothScrollAnimation()
if win.smoothScrollAnimation.State() == core.QAbstractAnimation__Running {
win.smoothScrollAnimation.Stop()
scrollingDelta := float64(win.scrollPixels2) / float64(win.getFont().lineHeight)
win.scrollDelta = delta + scrollingDelta
// win.snapshot = win.combinePixmap(win.snapshot, win.grabScreen(), scrollingDelta)
win.smoothScrollAnimation.SetStartValue(core.NewQVariant10(win.scrollDelta))
win.smoothScrollAnimation.SetEndValue(core.NewQVariant10(0))
win.smoothScrollAnimation.Start(core.QAbstractAnimation__DeletionPolicy(core.QAbstractAnimation__KeepWhenStopped))
} else {
win.scrollDelta = delta
win.smoothScrollAnimation.SetStartValue(core.NewQVariant10(win.scrollDelta))
win.smoothScrollAnimation.SetEndValue(core.NewQVariant10(0))
win.smoothScrollAnimation.Start(core.QAbstractAnimation__DeletionPolicy(core.QAbstractAnimation__KeepWhenStopped))
}
}
func (hl *Highlight) fg() *RGBA {
var color *RGBA
if hl.reverse {
color = hl.background
if color == nil {
// color = w.s.ws.background
color = editor.colors.bg
}
} else {
color = hl.foreground
if color == nil {
// color = w.s.ws.foreground
color = editor.colors.fg
}
}
return color
}
func (hl *Highlight) bg() *RGBA {
var color *RGBA
if hl.reverse {
color = hl.foreground
if color == nil {
// color = w.s.ws.foreground
color = editor.colors.fg
}
} else {
color = hl.background
if color == nil {
// color = w.s.ws.background
color = editor.colors.bg
}
}
return color
}
func (win *Window) updateGridContent(row, colStart int, cells []interface{}) {
if colStart < 0 {
return
}
if row >= win.rows {
return
}
// Suppresses flickering during smooth scrolling
if win.scrollPixels[0] != 0 {
win.scrollPixels[0] = 0
}
if win.scrollPixels[1] != 0 {
win.scrollPixels[1] = 0
}
lenContent, doNotCountContent, isPartialUpdate := win.updateLine(row, colStart, cells)
if !editor.config.Editor.IndentGuide {
if !doNotCountContent && !isPartialUpdate {
win.countContent(row)
} else if doNotCountContent {
win.lenContent[row] = lenContent
}
} else {
win.countContent(row)
}
if !win.isShown() {
win.show()
}
// Related to #364, it seems that in a UI consisting of multiple float windows,
// there are cases where the grid in which the grid_line event is emitted
// must be considered in the z-order of the UI.
if win.isFloatWin && !win.isMsgGrid {
if !editor.isExtWinNowInactivated && !editor.isWindowNowInactivated {
if win.s.lastGridLineGrid != win.grid {
win.zindex.order = globalOrder
globalOrder++
win.raise()
}
}
}
if win.grid == 1 && win.s.name == "minimap" {
return
}
if win.maxLenContent < win.lenContent[row] {
win.maxLenContent = win.lenContent[row]
}
}
func (w *Window) updateLine(row, col int, cells []interface{}) (int, bool, bool) {
line := w.content[row]
maskRow := w.contentMask[row]
colStart := col
hlAttrDef := w.s.hlAttrDef
linelen := len(line)
lenScaledChars := len(w.charsScaledLineHeight)
hl := -1
lastSpaces := 0
lenCells := len(cells)
for k, arg := range cells {
if col >= linelen {
continue
}
// cell
cell := arg.([]interface{})
// char of cell
char := cell[0].(string)
// is the char is scraled?
scaled := false
if lenScaledChars > 0 {
for _, charScaled := range w.charsScaledLineHeight {
if char == charScaled {
scaled = true
}
}
}
if len(cell) >= 2 {
hl = util.ReflectToInt(cell[1])
}
repeat := 1
if len(cell) == 3 {
repeat = util.ReflectToInt(cell[2])
// Count spaces in line end for culcurate content
if k == lenCells-1 {
if char == " " && hl == 0 {
lastSpaces = util.ReflectToInt(cell[2])
}
}
}
for ; repeat > 0 && col < linelen; repeat-- {
if line[col] == nil {
line[col] = &Cell{}
maskRow[col] = true
}
line[col].char = char
line[col].normalWidth = w.isNormalWidth(char)
line[col].scaled = scaled
// if w.grid == 2 {
// fmt.Printf(
// fmt.Sprintf("'%s',", line[col].char),
// )
// }
if hl != -1 || col == 0 {
line[col].highlight = hlAttrDef[hl]
} else {
line[col].highlight = line[col-1].highlight
}
maskRow[col] = line[col].char != " " ||
!line[col].highlight.bg().equals(w.background) ||
line[col].highlight.underline ||
line[col].highlight.undercurl ||
line[col].highlight.strikethrough ||
line[col].highlight.underdouble ||
line[col].highlight.underdotted ||
line[col].highlight.underdashed
if !w.isPopupmenu &&
(line[col].highlight.uiName == "Pmenu" ||
line[col].highlight.uiName == "PmenuSel" ||
line[col].highlight.uiName == "PmenuSbar") {
w.isPopupmenu = true
w.move(w.pos[0], w.pos[1], w.anchorwin)
}
if line[col].highlight.blend > 0 {
if w.wb != line[col].highlight.blend {
w.s.ws.screen.purgeTextCacheForWins()
}
w.wb = line[col].highlight.blend
}
col++
}
}
w.queueRedraw(colStart, row, col-colStart+1, 1)
lenContentRow := w.cols
if len(w.lenContent) >= row+1 {
lenContentRow = w.lenContent[row]
}
doNotCountContent1 := (col == w.cols && lastSpaces > 0)
doNotCountContent2 := (col == lenContentRow && lastSpaces > 0)
doNotCountContent := doNotCountContent1 || doNotCountContent2
isPartialUpdate := col < lenContentRow && lenContentRow < w.cols
newlenContent := 0
if doNotCountContent1 {
newlenContent = w.cols - lastSpaces
} else if doNotCountContent2 {
newlenContent = w.lenContent[row] - lastSpaces
}
return newlenContent, doNotCountContent, isPartialUpdate
}
func (w *Window) countContent(row int) {
line := w.content[row]
lenLine := w.cols - 1
width := w.cols - 1
var breakFlag0, breakFlag1 bool
if !editor.config.Editor.IndentGuide {
breakFlag0 = true
}
for j := w.cols - 1; j >= 0; j-- {
cell := line[j]
if !breakFlag0 {
if cell == nil || cell.char == " " {
lenLine--
} else {
breakFlag0 = true
}
}
if !breakFlag1 {
if cell == nil || (cell.char == " " && cell.highlight.bg().equals(w.background) &&
!cell.highlight.underline && !cell.highlight.undercurl &&
!cell.highlight.strikethrough && !cell.highlight.underdouble &&
!cell.highlight.underdotted && !cell.highlight.underdashed) {
width--
} else {
breakFlag1 = true
break
}
}
if breakFlag0 && breakFlag1 {
break
}
}
w.lenLine[row] = lenLine + 1
w.lenContent[row] = width + 1
}
// func (w *Window) makeUpdateMask(row, col int, cells []interface{}) {
// for j, cell := range w.content[row] {
// if cell == nil {
// w.contentMask[row][j] = true
// continue
//
// // If the target cell is blank and there is no text decoration of any kind
// } else if cell.char == " " &&
// cell.highlight.bg().equals(w.background) &&
// !cell.highlight.underline &&
// !cell.highlight.undercurl &&
// !cell.highlight.strikethrough {
//
// w.contentMask[row][j] = false
//
// } else {
// w.contentMask[row][j] = true
// }
// }
// }
func (w *Window) countHeadSpaceOfLine(y int) (int, error) {
if w == nil {
return 0, errors.New("window is nil")
}
if y >= len(w.content) || w.content == nil {
return 0, errors.New("content is nil")
}
line := w.content[y]
count := 0
for _, c := range line {
if c == nil {
continue
}
if c.char != " " && !c.highlight.isSignColumn() {
break
} else {
count++
}
}
return count, nil
}
func (h *Highlight) isSignColumn() bool {
switch h.hlName {
case "SignColumn",
"FoldColumn",
"LineNr",
"CursorLineNr",
"ALEErrorSign",
"ALEStyleErrorSign",
"ALEWarningSign",
"ALEStyleWarningSign",
"ALEInfoSign",
"ALESignColumnWithErrors",
"GitSignsAdd",
"GitSignsChange",
"GitSignsDelete",
"LspErrorHighlight",
"LspWarningHighlight",
"LspInformationHighlight",
"LspHintHighlight":
return true
default:
return false
}
}
func (w *Window) scroll(count int) {
top := w.scrollRegion[0]
bot := w.scrollRegion[1]
left := w.scrollRegion[2]
right := w.scrollRegion[3]
// If the rectangular area to be scrolled matches
// the entire area of the grid, we simply shift the content slice.
if top == 0 && bot == w.rows-1 {
c := count
if count < 0 {
c = c * -1
}
content := make([][]*Cell, c)
contentMask := make([][]bool, c)
lenLine := make([]int, c)
lenContent := make([]int, c)
for i := 0; i < c; i++ {
content[i] = make([]*Cell, w.cols)
contentMask[i] = make([]bool, w.cols)
}
if count > 0 {
w.content = append(w.content[count:], content...)
w.contentMask = append(w.contentMask[count:], contentMask...)
w.lenLine = append(w.lenLine[count:], lenLine...)
w.lenContent = append(w.lenContent[count:], lenContent...)
}
if count < 0 {
// w.content = w.content[:w.rows+count]
w.content = append(content, w.content...)
// w.contentMask = w.contentMask[:w.rows+count]
w.contentMask = append(contentMask, w.contentMask...)
// w.lenLine = w.lenLine[:w.rows+count]
w.lenLine = append(lenLine, w.lenLine...)
// w.lenContent = w.lenContent[:w.rows+count]
w.lenContent = append(lenContent, w.lenContent...)
}
} else {
// If the rectangular area to be scrolled does not match
// the entire area of the grid
if count > 0 {
for row := top; row <= bot-count; row++ {
w.scrollContentByCount(row, left, right, bot, count)
}
for row := bot - count + 1; row <= bot; row++ {
w.clearLinesWhereContentHasPassed(row, left, right)
}
}
if count < 0 {
for row := bot; row >= top-count; row-- {
w.scrollContentByCount(row, left, right, bot, count)
}
for row := top - count - 1; row >= top; row-- {
w.clearLinesWhereContentHasPassed(row, left, right)
}
}
}
// Suppresses flickering during smooth scrolling
if w.scrollPixels[0] != 0 {
w.scrollPixels[0] = 0
}
if w.scrollPixels[1] != 0 {
w.scrollPixels[1] = 0
}
// w.queueRedraw(left, top, (right - left + 1), (bot - top + 1))
w.queueRedraw(0, top, w.cols, bot-top+1)
}
// scrollContentByCount a function to shift the contents of w.content array by count.
func (w *Window) scrollContentByCount(row, left, right, bot, count int) {
if len(w.content) <= bot {
return
}
if len(w.content[row]) <= right {
return
}
for col := left; col <= right; col++ {
w.content[row][col] = w.content[row+count][col]
w.contentMask[row][col] = w.contentMask[row+count][col]
}
w.lenLine[row] = w.lenLine[row+count]
w.lenContent[row] = w.lenContent[row+count]
}
// clearLinesWhereContentHasPassed is a function to clear the source area
// after shifting the contents of w.content array by count.
func (w *Window) clearLinesWhereContentHasPassed(row, left, right int) {
if len(w.content) <= row {
return
}
if len(w.content[row]) <= right {
return
}
for col := left; col <= right; col++ {
w.content[row][col] = nil
w.contentMask[row][col] = true
}
}
func (w *Window) update() {
if w == nil {
return
}
w.redrawMutex.Lock()
font := w.getFont()
begin := w.queueRedrawArea[1]
end := w.queueRedrawArea[3]
extendedDrawingArea := int(font.cellwidth)
drawWithSingleRect := (w.lastScrollphase != core.Qt__ScrollEnd && (w.scrollPixels[0] != 0 || w.scrollPixels[1] != 0)) || editor.config.Editor.IndentGuide || w.s.name == "minimap" || (editor.config.Editor.SmoothScroll && w.scrollPixels2 != 0)
if drawWithSingleRect {
begin = 0
end = w.rows
}
// Mitigate #389
if runtime.GOOS == "windows" {
begin = 0
end = w.rows
}
for i := begin; i < end; i++ {
if len(w.content) <= i {
continue
}
width := w.lenContent[i]
contentMaskI := w.contentMask[i]
lenContentMaskI := len(contentMaskI)
lineHeightI := i * font.lineHeight
if width < w.lenOldContent[i] {
width = w.lenOldContent[i]
}
w.lenOldContent[i] = w.lenContent[i]
// If DrawIndentGuide is enabled
if editor.config.Editor.IndentGuide {
if i < w.rows-1 {
if width < w.lenContent[i+1] {
width = w.lenContent[i+1]
}
}
}
// If screen is minimap
if drawWithSingleRect && w.s.name == "minimap" {
width = w.cols
} else if drawWithSingleRect {
width = w.maxLenContent
}
width++
// Create rectangles that require updating.
var rects [][4]int
isCreateRect := false
if drawWithSingleRect {
rect := [4]int{
0,
lineHeightI,
int(math.Ceil(float64(width)*font.cellwidth)) + extendedDrawingArea,
font.lineHeight,
}
rects = append(rects, rect)
} else {
start := 0
for j, cm := range contentMaskI {
mask := cm || w.contentMaskOld[i][j]
// Starting point for creating a rectangular area
if mask && !isCreateRect {
start = j
isCreateRect = true
}
// Judgment point for end of rectangular area creation
if (!mask && isCreateRect) || (j >= lenContentMaskI-1 && isCreateRect) {
// If the next rectangular area will be created with only one cell separating it, merge it.
if j+1 <= lenContentMaskI-1 {
if contentMaskI[j+1] {
continue
}
}
jj := j
// If it reaches the edge of the grid
if j >= lenContentMaskI-1 && isCreateRect {
jj++
}
// create rectangular area
// To avoid leaving drawing debris, update a slightly larger area.
x := int(float64(start)*font.cellwidth) - 1
if x < 0 {
x = 0
}
rect := [4]int{
x, // update a slightly larger area.
lineHeightI,
int(math.Ceil(float64(jj-start)*font.cellwidth)) + extendedDrawingArea, // update a slightly larger area.
font.lineHeight,
}
rects = append(rects, rect)
isCreateRect = false
}
}
}
// Request screen refresh for each rectangle region.
for _, rect := range rects {
w.Update2(
rect[0],
rect[1],
rect[2],
rect[3],
)
}
// Update contentMaskOld
copy(w.contentMaskOld[i], contentMaskI)
}
// reset redraw area
w.queueRedrawArea[0] = w.cols
w.queueRedrawArea[1] = w.rows
w.queueRedrawArea[2] = 0
w.queueRedrawArea[3] = 0
w.redrawMutex.Unlock()
}
func (w *Window) queueRedrawAll() {
w.redrawMutex.Lock()
w.queueRedrawArea = [4]int{0, 0, w.cols, w.rows}
w.redrawMutex.Unlock()
}
func (w *Window) queueRedraw(x, y, width, height int) {
w.redrawMutex.Lock()
if x < w.queueRedrawArea[0] {
w.queueRedrawArea[0] = x
}
if y < w.queueRedrawArea[1] {
w.queueRedrawArea[1] = y
}
if (x + width) > w.queueRedrawArea[2] {
w.queueRedrawArea[2] = x + width
}
if (y + height) > w.queueRedrawArea[3] {
w.queueRedrawArea[3] = y + height
}
w.redrawMutex.Unlock()
}
func (w *Window) drawBackground(p *gui.QPainter, y int, col int, cols int, isDrawDefaultBg bool) {
if y >= len(w.content) {
return
}
line := w.content[y]
var bg *RGBA
// Set smooth scroll offset
var horScrollPixels, verScrollPixels int
// isDrawDefaultBg := true
// // Simply paint the color into a rectangle
// for x := col; x <= col+cols; x++ {
// if x >= len(line) {
// continue
// }
// var highlight *Highlight
// if line[x] == nil {
// highlight = w.s.hlAttrDef[0]
// } else {
// highlight = line[x].highlight
// }
// if !bg.equals(w.s.ws.background) || isDrawDefaultBg {
// // Set diff pattern
// pattern, color, transparent := w.getFillpatternAndTransparent(highlight)
// // Fill background with pattern
// rectF := core.NewQRectF4(
// float64(x)*font.cellwidth,
// float64((y)*font.lineHeight),
// font.cellwidth,
// float64(font.lineHeight),
// )
// p.FillRect(
// rectF,
// gui.NewQBrush3(
// gui.NewQColor3(
// color.R,
// color.G,
// color.B,
// transparent,
// ),
// pattern,
// ),
// )
// }
// }
if y < w.viewportMargins[0] || y > w.rows-w.viewportMargins[1]-1 {
verScrollPixels = 0
horScrollPixels = 0
isDrawDefaultBg = true
}
// The same color combines the rectangular areas and paints at once
var start, end int
var lastBg *RGBA
var lastHighlight, highlight *Highlight
for x := col; x <= col+cols; x++ {
if x >= len(line)+1 {
continue
}
if !(y < w.viewportMargins[0] || y > w.rows-w.viewportMargins[1]-1) {
if w.s.ws.mouseScroll != "" {
horScrollPixels = w.scrollPixels[0]
}
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
}
if x < len(line) {
if line[x] == nil {
highlight = w.s.hlAttrDef[0]
} else {
highlight = line[x].highlight
}
if line[x] != nil {
if line[x].covered {
highlight = w.s.hlAttrDef[0]
}
}
} else {
highlight = w.s.hlAttrDef[0]
}
if highlight.isSignColumn() {
horScrollPixels = 0
}
bg = highlight.bg()
bounds := col + cols
if col+cols > len(line) {
bounds = len(line)
}
if lastBg == nil {
start = x
end = x
lastBg = bg
lastHighlight = highlight
}
if lastBg != nil {
if lastBg.equals(bg) {
end = x
}
if x < w.viewportMargins[2] {
horScrollPixels = 0
w.fillCellRect(p, lastHighlight, lastBg, y, start, end, horScrollPixels, verScrollPixels, isDrawDefaultBg)
continue
}
if x > w.cols-w.viewportMargins[3]-1 {
horScrollPixels = 0
w.fillCellRect(p, lastHighlight, lastBg, y, start, end, horScrollPixels, verScrollPixels, isDrawDefaultBg)
start = x
end = x
if x == bounds {
w.fillCellRect(p, lastHighlight, lastBg, y, start, end, horScrollPixels, verScrollPixels, isDrawDefaultBg)
}
continue
}
if !lastBg.equals(bg) || x == bounds {
w.fillCellRect(p, lastHighlight, lastBg, y, start, end, horScrollPixels, verScrollPixels, isDrawDefaultBg)
start = x
end = x
lastBg = bg
lastHighlight = highlight
if x == bounds {
w.fillCellRect(p, lastHighlight, lastBg, y, start, end, horScrollPixels, verScrollPixels, isDrawDefaultBg)
}
}
}
}
w.drawMsgSep(p)
}
func (w *Window) fillCellRect(p *gui.QPainter, lastHighlight *Highlight, lastBg *RGBA, y, start, end, horScrollPixels, verScrollPixels int, isDrawDefaultBg bool) {
if lastHighlight == nil {
return
}
// If the background color to be painted is a Normal highlight group and another float window
// that covers the float window and is closest in z-order has the same background color,
// the background color should not be painted.
if w.isFloatWin && !w.isMsgGrid {
if w.zindex.nearestLowerZOrderWindow != nil && w.zindex.nearestLowerZOrderWindow.isFloatWin {
if lastHighlight.uiName == "NormalFloat" || lastHighlight.uiName == "NormalNC" {
if w.s.getHighlightByUiname("Normal").bg().Hex() == lastHighlight.bg().Hex() {
return
}
}
}
}
width := end - start + 1
if width < 0 {
width = 0
}
if !isDrawDefaultBg && lastBg.equals(w.background) {
width = 0
}
if lastHighlight.isSignColumn() {
horScrollPixels = 0
}
if width == 0 {
return
}
font := w.getFont()
if verScrollPixels == 0 ||
verScrollPixels > 0 && (y < w.rows-w.viewportMargins[1]-1) ||
verScrollPixels < 0 && (y > w.viewportMargins[0]) {
if editor.config.Editor.CachedDrawing {
cache := w.getCache()
if cache == (Cache{}) {
return
}
var image *gui.QImage
imagev, err := cache.get(HlBgKey{
bg: lastHighlight.bg(),
length: width,
})
if err != nil {
image = w.newBgCache(lastHighlight, width)
w.setBgCache(lastHighlight, width, image)
} else {
image = imagev.(*gui.QImage)
}
p.DrawImage9(
int(float64(start)*font.cellwidth+float64(horScrollPixels)),
int(float64((y)*font.lineHeight+verScrollPixels)),
image,
0, 0,
-1, -1,
core.Qt__AutoColor,
)
} else {
// Set diff pattern
pattern, color, transparent := w.getFillpatternAndTransparent(lastHighlight)
// Fill background with pattern
rectF := core.NewQRectF4(
float64(start)*font.cellwidth+float64(horScrollPixels),
float64((y)*font.lineHeight+verScrollPixels),
float64(width)*font.cellwidth,
float64(font.lineHeight),
)
p.FillRect(
rectF,
gui.NewQBrush3(
gui.NewQColor3(
color.R,
color.G,
color.B,
transparent,
),
pattern,
),
)
}
}
// Addresses an issue where smooth scrolling with a touchpad causes incomplete
// background rendering at the top or bottom of floating windows.
// Adds compensation drawing for areas partially scrolled into view by checking
// `verScrollPixels` and filling the necessary background to prevent visual gaps.
pattern, color, transparent := w.getFillpatternAndTransparent(lastHighlight)
if verScrollPixels > 0 {
if y == w.viewportMargins[0] {
ypos := float64((y) * font.lineHeight)
if verScrollPixels < 0 {
ypos = ypos + float64(font.lineHeight+verScrollPixels)
}
// Fill background with pattern
rectF := core.NewQRectF4(
float64(start)*font.cellwidth+float64(horScrollPixels),
ypos,
float64(width)*font.cellwidth,
math.Abs(float64(verScrollPixels)),
)
p.FillRect(
rectF,
gui.NewQBrush3(
gui.NewQColor3(
color.R,
color.G,
color.B,
transparent,
),
pattern,
),
)
}
if y == w.rows-w.viewportMargins[1]-1 {
// Fill background with pattern
rectF := core.NewQRectF4(
float64(start)*font.cellwidth+float64(horScrollPixels),
float64((y)*font.lineHeight+verScrollPixels),
float64(width)*font.cellwidth,
float64(font.lineHeight-verScrollPixels),
)
p.FillRect(
rectF,
gui.NewQBrush3(
gui.NewQColor3(
color.R,
color.G,
color.B,
transparent,
),
pattern,
),
)
}
}
if verScrollPixels < 0 {
if y == w.viewportMargins[0] {
// Fill background with pattern
rectF := core.NewQRectF4(
float64(start)*font.cellwidth+float64(horScrollPixels),
float64((y)*font.lineHeight),
float64(width)*font.cellwidth,
float64(font.lineHeight+verScrollPixels),
)
p.FillRect(
rectF,
gui.NewQBrush3(
gui.NewQColor3(
color.R,
color.G,
color.B,
transparent,
),
pattern,
),
)
}
if y == w.rows-w.viewportMargins[1]-1 {
ypos := float64((y) * font.lineHeight)
ypos = ypos + float64(font.lineHeight+verScrollPixels)
// Fill background with pattern
rectF := core.NewQRectF4(
float64(start)*font.cellwidth+float64(horScrollPixels),
ypos,
float64(width)*font.cellwidth,
math.Abs(float64(verScrollPixels)),
)
p.FillRect(
rectF,
gui.NewQBrush3(
gui.NewQColor3(
color.R,
color.G,
color.B,
transparent,
),
pattern,
),
)
}
}
}
func (w *Window) newBgCache(lastHighlight *Highlight, length int) *gui.QImage {
font := w.getFont()
width := float64(length) * font.cellwidth
height := float64(font.lineHeight)
image := gui.NewQImage3(
int(w.devicePixelRatio*width),
int(w.devicePixelRatio*height),
gui.QImage__Format_ARGB32_Premultiplied,
)
image.SetDevicePixelRatio(w.devicePixelRatio)
// Set diff pattern
_, color, transparent := w.getFillpatternAndTransparent(lastHighlight)
image.Fill2(
gui.NewQColor3(
color.R,
color.G,
color.B,
transparent,
),
)
return image
}
func (w *Window) setBgCache(highlight *Highlight, length int, image *gui.QImage) {
if w.font != nil {
w.cache.set(
HlBgKey{
bg: highlight.bg(),
length: length,
},
image,
)
} else {
w.s.cache.set(
HlBgKey{
bg: highlight.bg(),
length: length,
},
image,
)
}
}
func (w *Window) drawMsgSep(p *gui.QPainter) {
if !w.isMsgGrid {
return
}
if !editor.config.Message.ShowMessageSeparators {
return
}
hl := w.s.getHighlightByUiname("MsgSeparator")
color := hl.bg().QColor()
p.FillRect5(
0,
0,
w.Width(),
1,
color,
)
}
func resolveFontFallback(font *Font, fallbackfonts []*Font, char string) *Font {
if len(fallbackfonts) == 0 {
return font
}
hasGlyph := font.hasGlyph(char)
if hasGlyph {
return font
} else {
for _, ff := range fallbackfonts {
hasGlyph = ff.hasGlyph(char)
if hasGlyph {
return ff
}
}
}
return font
}
func (w *Window) drawText(p *gui.QPainter, y int, col int, cols int) {
if y >= len(w.content) {
return
}
wsfont := w.getFont()
if !editor.config.Editor.CachedDrawing {
p.SetFont(wsfont.qfont)
}
line := w.content[y]
chars := map[*Highlight][]int{}
specialChars := []int{}
cellBasedDrawing := editor.config.Editor.DisableLigatures || (editor.config.Editor.Letterspace > 0)
wsfontLineHeight := y * wsfont.lineHeight
// Set smooth scroll offset
var horScrollPixels, verScrollPixels int
for x := col; x <= col+cols; x++ {
if x >= len(line) {
continue
}
if line[x] == nil {
continue
}
if line[x].char == "" {
continue
}
if line[x].char == " " {
continue
}
if !line[x].normalWidth {
specialChars = append(specialChars, x)
continue
}
if line[x].scaled {
specialChars = append(specialChars, x)
continue
}
// If the ligature setting is disabled,
// we will draw the characters on the screen one by one.
if cellBasedDrawing {
if line[x].covered && w.grid == 1 {
continue
}
if w.s.ws.mouseScroll != "" {
horScrollPixels = w.scrollPixels[0]
}
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
if line[x].highlight.isSignColumn() {
horScrollPixels = 0
}
if x < w.viewportMargins[2] || x > w.cols-w.viewportMargins[3]-1 {
horScrollPixels = 0
verScrollPixels = 0
}
if y < w.viewportMargins[0] || y > w.rows-w.viewportMargins[1]-1 {
horScrollPixels = 0
verScrollPixels = 0
}
w.drawTextInPos(
p,
int(float64(x)*wsfont.cellwidth)+horScrollPixels,
wsfontLineHeight+verScrollPixels,
line[x].char,
line[x].highlight,
true,
line[x].scaled,
)
} else {
// Prepare to draw a group of identical highlight units.
highlight := line[x].highlight
if x < w.viewportMargins[2] || x > w.cols-w.viewportMargins[3]-1 {
highlight.special = highlight.special.copy()
highlight.foreground = highlight.foreground.copy()
highlight.background = highlight.background.copy()
}
colorSlice, ok := chars[highlight]
if !ok {
colorSlice = []int{}
}
colorSlice = append(colorSlice, x)
chars[highlight] = colorSlice
}
}
// This is the normal rendering process for goneovim,
// we draw a word snippet of the same highlight on the screen for each of the highlights.
if !cellBasedDrawing {
for highlight, colorSlice := range chars {
var buffer bytes.Buffer
slice := colorSlice
isIndentationWhiteSpace := true
pos := col
for x := col; x <= col+cols; x++ {
if w.s.ws.mouseScroll != "" {
horScrollPixels = w.scrollPixels[0]
}
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
if highlight.isSignColumn() {
horScrollPixels = 0
}
if x < w.viewportMargins[2] || x > w.cols-w.viewportMargins[3]-1 {
horScrollPixels = 0
verScrollPixels = 0
}
if y < w.viewportMargins[0] || y > w.rows-w.viewportMargins[1]-1 {
horScrollPixels = 0
verScrollPixels = 0
}
isDrawWord := false
index := slice[0]
if len(slice) != 0 {
// e.g. when the contents of the line is;
// [ 'a', 'b', ' ', 'c', ' ', ' ', 'd', 'e', 'f' ]
//
// then, the slice is [ 1,2,4,7,8,9 ]
// the following process is
// * If a word is separated by a single space, it is treated as a single word.
// * If there are more than two continuous spaces, each word separated by a space
// is treated as an independent word.
//
// therefore, the above example will treet that;
// "ab c" and "def"
if x != index {
if isIndentationWhiteSpace {
continue
} else {
if len(slice) > 1 {
if x+1 == index {
if buffer.Len() > 0 {
pos++
buffer.WriteString(" ")
}
} else {
isDrawWord = true
}
} else {
isDrawWord = true
}
}
}
if x == index {
pos++
char := line[x].char
if line[x].covered && w.grid == 1 {
char = " "
}
buffer.WriteString(char)
slice = slice[1:]
isIndentationWhiteSpace = false
}
}
if isDrawWord || len(slice) == 0 {
if len(slice) == 0 {
x++
}
if buffer.Len() != 0 {
w.drawTextInPos(
p,
int(float64(x-pos)*wsfont.cellwidth)+horScrollPixels,
wsfontLineHeight+verScrollPixels,
buffer.String(),
highlight,
true,
false,
)
buffer.Reset()
isDrawWord = false
pos = 0
}
if len(slice) == 0 {
break
}
}
}
}
}
if len(specialChars) >= 1 {
for _, x := range specialChars {
if line[x] == nil {
continue
}
char := line[x].char
if line[x].covered && w.grid == 1 {
char = " "
}
if char == " " {
continue
}
if w.s.ws.mouseScroll != "" {
horScrollPixels = w.scrollPixels[0]
}
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
if line[x].highlight.isSignColumn() {
horScrollPixels = 0
}
if x < w.viewportMargins[2] || x > w.cols-w.viewportMargins[3]-1 {
horScrollPixels = 0
verScrollPixels = 0
}
if y < w.viewportMargins[0] || y > w.rows-w.viewportMargins[1]-1 {
horScrollPixels = 0
verScrollPixels = 0
}
w.drawTextInPos(
p,
int(float64(x)*wsfont.cellwidth)+horScrollPixels,
wsfontLineHeight+verScrollPixels,
line[x].char,
line[x].highlight,
false,
line[x].scaled,
)
}
}
}
func (w *Window) drawTextInPos(p *gui.QPainter, x, y int, text string, highlight *Highlight, isNormalWidth bool, scaled bool) {
wsfont := w.getFont()
// var horScrollPixels int
// horScrollPixels = w.scrollPixels[0]
// if highlight.isSignColumn() {
// horScrollPixels = 0
// }
// if CachedDrawing is disabled
if !editor.config.Editor.CachedDrawing {
w.drawTextInPosWithNoCache(
p,
x, //+horScrollPixels,
y+wsfont.shift,
text,
highlight,
isNormalWidth,
scaled,
)
} else { // if CachedDrawing is enabled
w.drawTextInPosWithCache(
p,
x, //+horScrollPixels,
y,
text,
highlight,
isNormalWidth,
scaled,
)
}
}
func (w *Window) drawTextInPosWithNoCache(p *gui.QPainter, x, y int, text string, highlight *Highlight, isNormalWidth bool, scaled bool) {
if text == "" {
return
}
var fontfallbacked *Font
if !isASCII(text) && w.font == nil && w.s.fontwide != nil {
fontfallbacked = resolveFontFallback(w.s.fontwide, w.s.fallbackfontwides, text)
} else {
if w.font == nil {
fontfallbacked = resolveFontFallback(w.s.font, w.s.fallbackfonts, text)
} else {
fontfallbacked = resolveFontFallback(w.font, w.fallbackfonts, text)
}
}
p.SetFont(fontfallbacked.qfont)
font := p.Font()
fg := highlight.fg()
p.SetPen2(fg.QColor())
if highlight.bold {
font.SetBold(true)
} else {
font.SetBold(false)
}
if highlight.italic {
font.SetItalic(true)
} else {
font.SetItalic(false)
}
// p.DrawText(point, text)
p.DrawText3(x, y, text)
}
func (w *Window) drawTextInPosWithCache(p *gui.QPainter, x, y int, text string, highlight *Highlight, isNormalWidth bool, scaled bool) {
if text == "" {
return
}
cache := w.getCache()
var image *gui.QImage
imagev, err := cache.get(HlTextKey{
text: text,
fg: highlight.fg(),
italic: highlight.italic,
bold: highlight.bold,
})
if err != nil {
image = w.newTextCache(text, highlight, isNormalWidth)
w.setTextCache(text, highlight, image)
} else {
image = imagev.(*gui.QImage)
}
// return if image is invalid
if image.Width() == 0 && image.Height() == 0 {
return
}
// Scale specific characters to full line height
yOffset := 0
if scaled {
font := w.getFont()
ratio := (float64(font.lineHeight) / float64(font.height))
if ratio != 1.0 {
newHeight := int(math.Floor(w.devicePixelRatio * float64(font.lineHeight) * ratio))
newSpace := int(math.Ceil(float64(font.lineSpace) * ratio / 2.0))
yOffset = newSpace
image = image.Scaled2(
image.Width(),
newHeight,
core.Qt__IgnoreAspectRatio,
core.Qt__SmoothTransformation,
)
}
}
p.DrawImage9(
x, y-yOffset,
image,
0, 0,
-1, -1,
core.Qt__AutoColor,
)
}
func (w *Window) setDecorationCache(highlight *Highlight, image *gui.QImage) {
if w.font != nil {
// If window has own font setting
w.cache.set(
HlDecorationKey{
fg: highlight.foreground,
bg: highlight.background,
sp: highlight.special,
underline: highlight.underline,
undercurl: highlight.undercurl,
strikethrough: highlight.strikethrough,
underdouble: highlight.underdouble,
underdotted: highlight.underdotted,
underdashed: highlight.underdashed,
},
image,
)
} else {
// screen text cache
w.s.cache.set(
HlDecorationKey{
fg: highlight.foreground,
bg: highlight.background,
sp: highlight.special,
underline: highlight.underline,
undercurl: highlight.undercurl,
strikethrough: highlight.strikethrough,
underdouble: highlight.underdouble,
underdotted: highlight.underdotted,
underdashed: highlight.underdashed,
},
image,
)
}
}
func (w *Window) newDecorationCache(char string, highlight *Highlight, isNormalWidth bool) *gui.QImage {
font := w.getFont()
width := font.cellwidth
// // Set smooth scroll offset
// var horScrollPixels, verScrollPixels int
// if w.s.ws.mouseScroll != "" {
// horScrollPixels += w.scrollPixels[0]
// }
// if w.lastScrollphase != core.Qt__NoScrollPhase {
// verScrollPixels = w.scrollPixels2
// }
// if editor.config.Editor.LineToScroll == 1 {
// verScrollPixels += w.scrollPixels[1]
// }
// create QImage
image := gui.NewQImage3(
int(math.Ceil(w.devicePixelRatio*width)),
int(w.devicePixelRatio*float64(font.lineHeight)),
gui.QImage__Format_ARGB32_Premultiplied,
)
image.SetDevicePixelRatio(w.devicePixelRatio)
image.Fill3(core.Qt__transparent)
pi := gui.NewQPainter2(image)
w.drawDecoration(pi, highlight, font, 0, 0, int(width), 0, 0)
pi.DestroyQPainter()
return image
}
func (w *Window) setTextCache(text string, highlight *Highlight, image *gui.QImage) {
if w.font != nil {
// If window has own font setting
w.cache.set(
HlTextKey{
text: text,
fg: highlight.fg(),
italic: highlight.italic,
bold: highlight.bold,
},
image,
)
} else {
// screen text cache
w.s.cache.set(
HlTextKey{
text: text,
fg: highlight.fg(),
italic: highlight.italic,
bold: highlight.bold,
},
image,
)
}
}
func (w *Window) initImagePainter() {
if w.imagePainter == nil {
w.imagePainter = gui.NewQPainter()
}
}
func (w *Window) destroyImagePainter() {
if w.imagePainter != nil {
w.imagePainter.DestroyQPainter()
w.imagePainter = nil
}
}
func (w *Window) newTextCache(text string, highlight *Highlight, isNormalWidth bool) *gui.QImage {
// * Ref: https://stackoverflow.com/questions/40458515/a-best-way-to-draw-a-lot-of-independent-characters-in-qt5/40476430#40476430
editor.putLog("start creating word cache:", text)
font := w.getFont()
var fontfallbacked *Font
if !isASCII(text) && w.font == nil && w.s.fontwide != nil {
fontfallbacked = resolveFontFallback(w.s.fontwide, w.s.fallbackfontwides, text)
} else {
if w.font == nil {
fontfallbacked = resolveFontFallback(w.s.font, w.s.fallbackfonts, text)
} else {
fontfallbacked = resolveFontFallback(w.font, w.fallbackfonts, text)
}
}
// Put debug log
if editor.opts.Debug != "" {
fi := gui.NewQFontInfo(font.qfont)
editor.putLog(
"Outputs font information creating word cache:",
fi.Family(),
fi.PointSizeF(),
fi.StyleName(),
fmt.Sprintf("%v", fi.PointSizeF()),
)
}
width := float64(len(text))*font.cellwidth + 1
if highlight.italic {
width = float64(len(text))*font.italicWidth + 1
}
fg := highlight.fg()
if !isNormalWidth {
advance := fontfallbacked.fontMetrics.HorizontalAdvance(text, -1)
if advance > 0 {
width = advance
}
}
// QImage default device pixel ratio is 1.0,
// So we set the correct device pixel ratio
// image := gui.NewQImage2(
// core.NewQRectF4(
// 0,
// 0,
// w.devicePixelRatio*width,
// w.devicePixelRatio*float64(font.lineHeight),
// ).Size().ToSize(),
// gui.QImage__Format_ARGB32_Premultiplied,
// )
image := gui.NewQImage3(
int(math.Ceil(w.devicePixelRatio*width)),
int(w.devicePixelRatio*float64(font.lineHeight)),
gui.QImage__Format_ARGB32_Premultiplied,
)
image.SetDevicePixelRatio(w.devicePixelRatio)
image.Fill3(core.Qt__transparent)
w.initImagePainter()
w.imagePainter.Begin(image)
// pi := gui.NewQPainter2(image)
w.imagePainter.SetPen2(fg.QColor())
w.imagePainter.SetFont(fontfallbacked.qfont)
if highlight.bold {
w.imagePainter.Font().SetBold(true)
// w.imagePainter.Font().SetWeight(font.qfont.Weight() + 50)
}
if highlight.italic {
w.imagePainter.Font().SetItalic(true)
}
w.imagePainter.DrawText6(
core.NewQRectF4(
0,
0,
width,
float64(font.lineHeight),
), text, gui.NewQTextOption2(core.Qt__AlignVCenter),
)
w.imagePainter.End()
// pi.DestroyQPainter()
editor.putLog("finished creating word cache:", text)
if !isNormalWidth {
image = scaleToGridCell(
image,
float64(font.cellwidth)*2.0/width,
)
}
return image
}
func scaleToGridCell(image *gui.QImage, ratio float64) *gui.QImage {
if ratio >= 1.0 {
return image
}
return image.Scaled2(
int(float64(image.Width())*ratio),
int(float64(image.Height())*ratio),
core.Qt__IgnoreAspectRatio,
core.Qt__SmoothTransformation,
)
}
func (w *Window) drawForeground(p *gui.QPainter, y int, col int, cols int) {
if w.s.name == "minimap" {
w.drawMinimap(p, y, col, cols)
} else {
// w.drawText(p, y, col, cols)
w.drawText(p, y, 0, w.cols)
w.drawTextDecoration(p, y, col, cols)
}
}
func (w *Window) drawTextDecoration(p *gui.QPainter, y int, col int, cols int) {
if y >= len(w.content) {
return
}
line := w.content[y]
font := w.getFont()
cache := w.getCache()
// Set smooth scroll offset
var horScrollPixels, verScrollPixels int
if w.s.ws.mouseScroll != "" {
horScrollPixels += w.scrollPixels[0]
}
if w.lastScrollphase != core.Qt__NoScrollPhase {
verScrollPixels = w.scrollPixels2
}
if editor.config.Editor.LineToScroll == 1 {
verScrollPixels += w.scrollPixels[1]
}
for x := col; x <= col+cols; x++ {
if x >= len(line) {
continue
}
if line[x] == nil {
continue
}
highlight := line[x].highlight
if !highlight.underline &&
!highlight.undercurl &&
!highlight.strikethrough &&
!highlight.underdouble &&
!highlight.underdotted &&
!highlight.underdashed {
continue
}
if line[x].covered && w.grid == 1 {
continue
}
// if CachedDrawing is disabled
if !editor.config.Editor.CachedDrawing {
w.drawDecoration(p, highlight, font, y, x, x+1, verScrollPixels, horScrollPixels)
} else { // if CachedDrawing is enabled
var image *gui.QImage
imagev, err := cache.get(HlDecorationKey{
fg: highlight.foreground,
bg: highlight.background,
sp: highlight.special,
underline: highlight.underline,
undercurl: highlight.undercurl,
strikethrough: highlight.strikethrough,
underdouble: highlight.underdouble,
underdotted: highlight.underdotted,
underdashed: highlight.underdashed,
})
if err != nil {
image = w.newDecorationCache(line[x].char, highlight, line[x].normalWidth)
w.setDecorationCache(highlight, image)
} else {
image = imagev.(*gui.QImage)
}
p.DrawImage7(
core.NewQPointF3(
float64(x)*font.cellwidth+float64(horScrollPixels),
float64(y*font.lineHeight)+float64(verScrollPixels),
),
image,
)
}
}
}
func (w *Window) drawDecoration(p *gui.QPainter, highlight *Highlight, font *Font, row, x1, x2, verScrollPixels, horScrollPixels int) {
pen := gui.NewQPen()
var color *gui.QColor
sp := highlight.special
if sp != nil {
color = sp.QColor()
pen.SetColor(color)
} else {
color = highlight.fg().QColor()
pen.SetColor(color)
}
p.SetPen(pen)
start := float64(x1) * font.cellwidth
end := float64(x2) * font.cellwidth
if highlight.strikethrough {
drawStrikethrough(p, font, color, row, start, end, verScrollPixels, horScrollPixels)
}
if highlight.underline {
drawUnderline(p, font, color, row, start, end, verScrollPixels, horScrollPixels)
}
if highlight.undercurl {
drawUndercurl(p, font, color, row, start, end, verScrollPixels, horScrollPixels)
}
if highlight.underdouble {
drawUnderdouble(p, font, color, row, start, end, verScrollPixels, horScrollPixels)
}
if highlight.underdotted {
drawUnderdotted(p, font, color, row, start, end, verScrollPixels, horScrollPixels)
}
if highlight.underdashed {
drawUnderdashed(p, font, color, row, start, end, verScrollPixels, horScrollPixels)
}
}
func drawStrikethrough(p *gui.QPainter, font *Font, color *gui.QColor, row int, start, end float64, verScrollPixels, horScrollPixels int) {
space := float64(font.lineSpace) / 3.0
if math.Abs(space) > font.ascent/3.0 {
space = font.ascent / 3.0
}
space2 := float64(font.lineSpace)
if space2 < -1 {
space2 = float64(font.lineSpace) / 2.0
}
// descent := float64(font.height) - font.ascent
weight := int(math.Ceil(float64(font.height) / 16.0))
if weight < 1 {
weight = 1
}
width := int(end - start)
if width < 0 {
width = 0
}
Y := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent)*0.65 + float64(space2/2)
p.FillRect5(
int(start)+horScrollPixels,
int(Y),
width,
weight,
color,
)
}
func drawUnderline(p *gui.QPainter, font *Font, color *gui.QColor, row int, start, end float64, verScrollPixels, horScrollPixels int) {
space := float64(font.lineSpace) / 3.0
if math.Abs(space) > font.ascent/3.0 {
space = font.ascent / 3.0
}
space2 := float64(font.lineSpace)
if space2 < -1 {
space2 = float64(font.lineSpace) / 2.0
}
descent := float64(font.height) - font.ascent
weight := int(math.Ceil(float64(font.height) / 18.0))
if weight < 1 {
weight = 1
}
Y := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent) + descent*0.5 + float64(font.lineSpace/2) + space
width := int(end - start)
if width < 0 {
width = 0
}
p.FillRect5(
int(start)+horScrollPixels,
int(Y),
width,
weight,
color,
)
}
func drawUndercurl(p *gui.QPainter, font *Font, color *gui.QColor, row int, start, end float64, verScrollPixels, horScrollPixels int) {
space := float64(font.lineSpace) / 3.0
if math.Abs(space) > font.ascent/3.0 {
space = font.ascent / 3.0
}
space2 := float64(font.lineSpace)
if space2 < -1 {
space2 = float64(font.lineSpace) / 2.0
}
descent := float64(font.height) - font.ascent
weight := int(math.Ceil(float64(font.height) / 16.0))
if weight < 1 {
weight = 1
}
amplitude := descent*0.65 + float64(space2)
maxAmplitude := font.ascent / 8.0
if amplitude >= maxAmplitude {
amplitude = maxAmplitude
}
freq := 1.0
phase := 0.0
Y := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent+descent*0.3) + float64(space2/2) + space
Y2 := Y + amplitude*math.Sin(0)
point := core.NewQPointF3(start+float64(horScrollPixels), Y2)
path := gui.NewQPainterPath2(point)
for i := int(point.X()); i <= int(end); i++ {
Y2 = Y + amplitude*math.Sin(2*math.Pi*freq*float64(i)/font.cellwidth+phase)
path.LineTo(core.NewQPointF3(float64(i), Y2))
}
p.DrawPath(path)
}
func drawUnderdouble(p *gui.QPainter, font *Font, color *gui.QColor, row int, start, end float64, verScrollPixels, horScrollPixels int) {
space := float64(font.lineSpace) / 3.0
if math.Abs(space) > font.ascent/3.0 {
space = font.ascent / 3.0
}
space2 := float64(font.lineSpace)
if space2 < -1 {
space2 = float64(font.lineSpace) / 2.0
}
descent := float64(font.height) - font.ascent
weight := int(math.Ceil(float64(font.height) / 16.0))
if weight < 1 {
weight = 1
}
Y1 := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent) + descent*0.1 + float64(font.lineSpace/2) + space
Y2 := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent) + descent*0.8 + float64(font.lineSpace/2) + space
width := int(end - start)
if width < 0 {
width = 0
}
doubleLineWeight := int(math.Ceil(float64(font.height) / 20.0))
p.FillRect5(
int(start)+horScrollPixels,
int(Y1),
width,
doubleLineWeight,
color,
)
p.FillRect5(
int(start)+horScrollPixels,
int(Y2),
width,
doubleLineWeight,
color,
)
}
func drawUnderdotted(p *gui.QPainter, font *Font, color *gui.QColor, row int, start, end float64, verScrollPixels, horScrollPixels int) {
space := float64(font.lineSpace) / 3.0
if math.Abs(space) > font.ascent/3.0 {
space = font.ascent / 3.0
}
space2 := float64(font.lineSpace)
if space2 < -1 {
space2 = float64(font.lineSpace) / 2.0
}
descent := float64(font.height) - font.ascent
weight := int(math.Ceil(float64(font.height) / 16.0))
if weight < 1 {
weight = 1
}
dottedWeight := int(float64(weight) * 0.8)
if dottedWeight < 1 {
dottedWeight = 1
}
pen := gui.NewQPen()
pen.SetWidth(dottedWeight)
pen.SetColor(color)
pen.SetStyle(core.Qt__DotLine)
p.SetPen(pen)
Y := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent) + descent*0.5 + float64(font.lineSpace/2) + space
p.DrawLine3(
int(start)+horScrollPixels,
int(Y),
int(end)+horScrollPixels,
int(Y),
)
}
func drawUnderdashed(p *gui.QPainter, font *Font, color *gui.QColor, row int, start, end float64, verScrollPixels, horScrollPixels int) {
space := float64(font.lineSpace) / 3.0
if math.Abs(space) > font.ascent/3.0 {
space = font.ascent / 3.0
}
space2 := float64(font.lineSpace)
if space2 < -1 {
space2 = float64(font.lineSpace) / 2.0
}
descent := float64(font.height) - font.ascent
weight := int(math.Ceil(float64(font.height) / 16.0))
if weight < 1 {
weight = 1
}
width := int((end - start) / 2)
if width < 0 {
width = 0
}
Y := float64(row*font.lineHeight+verScrollPixels) + float64(font.ascent) + descent*0.5 + float64(font.lineSpace/2) + space
p.FillRect5(
int(start)+int(math.Ceil(font.cellwidth*0.25))+horScrollPixels,
int(Y),
// int(math.Ceil(font.cellwidth*0.5)),
width,
weight,
color,
)
}
func (w *Window) getFillpatternAndTransparent(hl *Highlight) (core.Qt__BrushStyle, *RGBA, int) {
color := hl.bg()
pattern := core.Qt__BrushStyle(1)
var t int
// We do not use the editor's transparency for popupmenu, float window, message window.
// It is recommended to use pumblend or winblend or editor.config.Message.Transparent to get those transparencies.
if w.isPopupmenu {
t = int(255 * ((100.0 - float64(w.s.ws.pb)) / 100.0))
} else if !w.isPopupmenu && w.isFloatWin && !w.isMsgGrid {
t = int(255 * ((100.0 - float64(w.wb)) / 100.0))
} else if w.isMsgGrid {
t = int(editor.config.Message.Transparent * 255.0)
} else {
t = int(editor.config.Editor.Transparent * 255.0)
}
if editor.config.Editor.DiffChangePattern != 1 && hl.hlName == "DiffChange" {
pattern = core.Qt__BrushStyle(editor.config.Editor.DiffChangePattern)
if editor.config.Editor.DiffChangePattern >= 7 &&
editor.config.Editor.DiffChangePattern <= 14 {
t = int(editor.config.Editor.Transparent * 255)
}
color = color.HSV().Colorfulness().RGB()
} else if editor.config.Editor.DiffDeletePattern != 1 && hl.hlName == "DiffDelete" {
pattern = core.Qt__BrushStyle(editor.config.Editor.DiffDeletePattern)
if editor.config.Editor.DiffDeletePattern >= 7 &&
editor.config.Editor.DiffDeletePattern <= 14 {
t = int(editor.config.Editor.Transparent * 255)
}
color = color.HSV().Colorfulness().RGB()
} else if editor.config.Editor.DiffAddPattern != 1 && hl.hlName == "DiffAdd" {
pattern = core.Qt__BrushStyle(editor.config.Editor.DiffAddPattern)
if editor.config.Editor.DiffAddPattern >= 7 &&
editor.config.Editor.DiffAddPattern <= 14 {
t = int(editor.config.Editor.Transparent * 255)
}
color = color.HSV().Colorfulness().RGB()
}
return pattern, color, t
}
// func isCJK(char rune) bool {
// if unicode.Is(unicode.Han, char) {
// return true
// }
// if unicode.Is(unicode.Hiragana, char) {
// return true
// }
// if unicode.Is(unicode.Katakana, char) {
// return true
// }
// if unicode.Is(unicode.Hangul, char) {
// return true
// }
//
// return false
// }
// isNormalWidth is:
// On Windows, HorizontalAdvance() may take a long time to get the width of CJK characters.
// For this reason, for CJK characters, the character width should be the double width of ASCII characters.
// This issue may also be related to the following.
// https://github.com/equalsraf/neovim-qt/issues/614
func (w *Window) isNormalWidth(char string) bool {
if len(char) == 0 {
return true
}
if isASCII(char) {
return true
}
var fontfallbacked *Font
if w.font == nil {
fontfallbacked = resolveFontFallback(w.s.font, w.s.fallbackfonts, char)
} else {
fontfallbacked = resolveFontFallback(w.font, w.fallbackfonts, char)
}
return fontfallbacked.fontMetrics.HorizontalAdvance(char, -1) <= w.getFont().cellwidth
}
func isASCII(c string) bool {
r := []rune(c)[0]
return r >= 0 && r <= 127
}
func (w *Window) deleteExternalWin() {
if w.extwin != nil {
w.extwin.Hide()
w.extwin.DestroyQDialog()
w.extwin = nil
}
}
func (w *Window) setResizableForExtWin() {
if !w.extwinConnectResizable {
w.extwin.ConnectResizeEvent(func(event *gui.QResizeEvent) {
height := w.extwin.Height() - EXTWINBORDERSIZE*2
width := w.extwin.Width() - EXTWINBORDERSIZE*2
cols := int((float64(width) / w.getFont().cellwidth))
rows := height / w.getFont().lineHeight
w.extwinResized = true
w.extwinManualResized = true
_ = w.s.ws.nvim.TryResizeUIGrid(w.grid, cols, rows)
})
w.extwinConnectResizable = true
}
}
func (w *Window) getCache() Cache {
if w.font != nil {
return w.cache
}
return w.s.cache
}
func newWindow() *Window {
// widget := widgets.NewQWidget(nil, 0)
win := NewWindow(nil, 0)
// cursor.StackUnder(win)
// cursor.raise()
win.SetContentsMargins(0, 0, 0, 0)
win.SetAttribute(core.Qt__WA_OpaquePaintEvent, true)
win.SetStyleSheet(" * { background-color: rgba(0, 0, 0, 0);}")
win.scrollRegion = []int{0, 0, 0, 0}
win.background = editor.colors.bg
win.lastMouseEvent = &inputMouseEvent{}
win.zindex = &zindex{}
win.charsScaledLineHeight = editor.config.Editor.CharsScaledLineHeight
win.SetAcceptDrops(true)
win.ConnectPaintEvent(win.paint)
win.ConnectDragEnterEvent(win.dragEnterEvent)
win.ConnectDragMoveEvent(win.dragMoveEvent)
win.ConnectDropEvent(win.dropEvent)
win.ConnectDestroyed(func(*core.QObject) {
win.destroyImagePainter()
})
// HideMouseWhenTyping process
if editor.config.Editor.HideMouseWhenTyping {
win.InstallEventFilter(win)
win.SetMouseTracking(true)
}
win.ConnectEventFilter(func(watched *core.QObject, event *core.QEvent) bool {
switch event.Type() {
case core.QEvent__MouseMove:
if editor.isHideMouse && editor.config.Editor.HideMouseWhenTyping {
gui.QGuiApplication_RestoreOverrideCursor()
editor.isHideMouse = false
}
default:
}
return win.EventFilterDefault(watched, event)
})
return win
}
func (win *Window) initializeOrReuseSmoothScrollAnimation() {
if win.smoothScrollAnimation == nil {
win.smoothScrollAnimation = core.NewQPropertyAnimation2(win, core.NewQByteArray2("scrollDiff", -1), win)
win.smoothScrollAnimation.SetEasingCurve(core.NewQEasingCurve(core.QEasingCurve__OutExpo))
win.smoothScrollAnimation.SetDuration(editor.config.Editor.SmoothScrollDuration)
win.smoothScrollAnimation.ConnectValueChanged(func(value *core.QVariant) {
ok := false
v := value.ToDouble(&ok)
if !ok {
return
}
font := win.getFont()
win.scrollPixels2 = int(v * float64(font.lineHeight))
// var x, y int
// win.Update2(
// x+win.viewportMargins[2]*int(font.cellwidth),
// y+(win.viewportMargins[0]*font.lineHeight),
// int(float64(win.cols)*font.cellwidth)-win.viewportMargins[2]*int(font.cellwidth)-win.viewportMargins[3]*int(font.cellwidth),
// win.rows*font.lineHeight-(win.viewportMargins[0]*font.lineHeight)-(win.viewportMargins[1]*font.lineHeight),
// )
var y int
win.Update2(
0,
y+(win.viewportMargins[0]*font.lineHeight),
int(float64(win.cols)*font.cellwidth),
win.rows*font.lineHeight-(win.viewportMargins[0]*font.lineHeight)-(win.viewportMargins[1]*font.lineHeight),
)
if v == 0 {
win.scrollPixels2 = 0
win.scrollDelta = 0
win.doErase = true
win.Update2(
0,
y+(win.viewportMargins[0]*font.lineHeight),
int(float64(win.cols)*font.cellwidth),
win.rows*font.lineHeight-(win.viewportMargins[0]*font.lineHeight)-(win.viewportMargins[1]*font.lineHeight),
)
win.doErase = false
win.fill()
}
})
}
}
func (w *Window) mouseEvent(event *gui.QMouseEvent) {
defer func() {
editor.isWindowNowActivated = false
editor.isWindowNowInactivated = false
editor.isExtWinNowActivated = false
editor.isExtWinNowInactivated = false
}()
if editor.config.Editor.IgnoreFirstMouseClickWhenAppInactivated {
if w.isExternal {
if editor.isExtWinNowActivated && !editor.isWindowNowInactivated {
return
}
} else {
if editor.isWindowNowActivated && !editor.isExtWinNowInactivated {
return
}
}
}
if w.lastMouseEvent == nil {
w.lastMouseEvent = &inputMouseEvent{}
}
if w.lastMouseEvent.event == event {
return
}
w.lastMouseEvent.event = event
if !w.s.ws.isMouseEnabled {
return
}
bt := event.Button()
if event.Type() == core.QEvent__MouseMove {
if event.Buttons()&core.Qt__LeftButton > 0 {
bt = core.Qt__LeftButton
} else if event.Buttons()&core.Qt__RightButton > 0 {
bt = core.Qt__RightButton
} else if event.Buttons()&core.Qt__MidButton > 0 {
bt = core.Qt__MidButton
}
}
button := ""
switch bt {
case core.Qt__LeftButton:
button += "left"
case core.Qt__RightButton:
button += "right"
case core.Qt__MidButton:
button += "middle"
case core.Qt__NoButton:
default:
}
action := ""
switch event.Type() {
case core.QEvent__MouseButtonDblClick:
action = "press"
case core.QEvent__MouseButtonPress:
action = "press"
case core.QEvent__MouseButtonRelease:
action = "release"
case core.QEvent__MouseMove:
action = "drag"
default:
}
mod := editor.modPrefix(event.Modifiers())
font := w.getFont()
col := int(float64(event.X()) / font.cellwidth)
row := int(float64(event.Y()) / float64(font.lineHeight))
if w.lastMouseEvent.button == button &&
w.lastMouseEvent.action == action &&
w.lastMouseEvent.mod == mod &&
w.lastMouseEvent.grid == w.grid &&
w.lastMouseEvent.row == row &&
w.lastMouseEvent.col == col {
return
}
w.lastMouseEvent.button = button
w.lastMouseEvent.action = action
w.lastMouseEvent.mod = mod
w.lastMouseEvent.grid = w.grid
w.lastMouseEvent.row = row
w.lastMouseEvent.col = col
w.s.ws.nvim.InputMouse(button, action, mod, w.grid, row, col)
}
func (w *Window) dragEnterEvent(e *gui.QDragEnterEvent) {
e.AcceptProposedAction()
}
func (w *Window) dragMoveEvent(e *gui.QDragMoveEvent) {
e.AcceptProposedAction()
}
func (w *Window) dropEvent(e *gui.QDropEvent) {
e.SetDropAction(core.Qt__CopyAction)
e.AcceptProposedAction()
e.SetAccepted(true)
w.focusGrid()
for _, i := range strings.Split(e.MimeData().Text(), "\n") {
data := strings.Split(i, "://")
if i != "" {
switch data[0] {
case "file":
buf, _ := w.s.ws.nvim.CurrentBuffer()
bufName, _ := w.s.ws.nvim.BufferName(buf)
var filepath string
switch data[1][0] {
case '/':
if runtime.GOOS == "windows" {
filepath = strings.Trim(data[1], `/`)
} else {
filepath = data[1]
}
default:
if runtime.GOOS == "windows" {
filepath = fmt.Sprintf(`//%s`, data[1])
} else {
filepath = data[1]
}
}
// Check if running under WSL and convert path if necessary
if editor.opts.Wsl != nil {
filepath = convertWindowsToUnixPath(filepath)
}
// if message grid is active and drop the file in message area,
// then, we put the filepath string into message area.
if w.isMsgGrid && w.s.ws.cursor.gridid == w.grid {
w.s.ws.nvim.FeedKeys(
filepath,
"t",
false,
)
} else {
if editor.config.Editor.ShowDiffDialogOnDrop && bufName != "" {
w.s.howToOpen(filepath)
} else {
fileOpenInBuf(filepath)
}
}
default:
}
}
}
}
func (w *Window) focusGrid() {
// If the window at the mouse pointer is not the current window
if w.grid != w.s.ws.cursor.gridid {
done := make(chan bool, 2)
go func() {
_ = w.s.ws.nvim.SetCurrentWindow(w.id)
done <- true
}()
select {
case <-done:
case <-time.After(NVIMCALLTIMEOUT * time.Millisecond):
}
}
}
// convertWindowsToUnixPath converts a Windows path to a Unix path as wslpath does.
func convertWindowsToUnixPath(winPath string) string {
unixPath := strings.ReplaceAll(winPath, `\`, `/`)
if len(unixPath) <= 2 {
return unixPath
}
// Convert drive letter to /mnt/<drive-letter>
if unixPath[1] == ':' {
driveLetter := strings.ToLower(string(unixPath[0]))
unixPath = fmt.Sprintf("/mnt/%s%s", driveLetter, unixPath[2:])
}
return unixPath
}
func (w *Window) isShown() bool {
if w == nil {
return false
}
return w.IsVisible()
}
func (w *Window) raise() {
if w.grid == 1 {
return
}
if !w.isFloatWin && !w.isExternal {
w.Raise()
}
w.s.setTopLevelGrid(w.grid)
// Float windows are re-stacked according to the "z-index" and generation order.
var floatWins []*Window
w.s.windows.Range(func(_, winITF interface{}) bool {
win := winITF.(*Window)
if win == nil {
return true
}
if win.isFloatWin && !win.isExternal {
floatWins = append(floatWins, win)
}
return true
})
sort.Slice(
floatWins,
func(i, j int) bool {
if floatWins[i].zindex.value < floatWins[j].zindex.value {
return true
} else if floatWins[i].zindex.value == floatWins[j].zindex.value {
if floatWins[i].zindex.order < floatWins[j].zindex.order {
return true
}
}
return false
},
)
// For each window object, set the pointer of closest window in the z-order
// that covers the target window on the region.
for i := 0; i < len(floatWins); i++ {
floatWins[i].Raise()
if i > 0 {
if floatWins[i].isMsgGrid {
continue
}
for j := i - 1; j >= 0; j-- {
if floatWins[j].isMsgGrid {
continue
}
if floatWins[j].Geometry().Contains2(floatWins[i].Geometry(), false) {
floatWins[i].zindex.nearestLowerZOrderWindow = floatWins[j]
}
}
}
}
// handle cursor widget
w.setUIParent()
}
func (w *Window) setUIParent() {
// Update cursor font
w.s.ws.cursor.updateFont(w, w.getFont(), w.getFallbackFonts())
defer func() {
w.s.ws.cursor.isInPalette = false
}()
// // ws := editor.workspaces[editor.active]
// prevCursorWin, ok := w.s.ws.screen.getWindow(w.s.ws.cursor.prevGridid)
// for handling external window
if !w.isExternal {
// Suppress window activation when font selection dialog is displayed
if w.s.ws.font != nil {
if w.s.ws.fontdialog != nil {
if !w.s.ws.fontdialog.IsVisible() {
editor.window.Raise()
}
}
}
w.s.ws.cursor.SetParent(w.s.widget)
if editor.config.Editor.ExtCmdline {
if w.s.ws.palette != nil {
w.s.ws.palette.setParent(w)
w.s.ws.palette.resize()
}
}
// if ok {
// if prevCursorWin.isExternal {
// w.s.ws.cursor.raise()
// }
// }
// if w.s.ws.cursor.isInPalette {
// w.s.ws.cursor.raise()
// }
} else if w.isExternal {
w.extwin.Raise()
w.s.ws.cursor.SetParent(w.extwin)
if editor.config.Editor.ExtCmdline {
if w.s.ws.palette != nil {
w.s.ws.palette.setParent(w)
w.s.ws.palette.resize()
}
}
// if ok {
// if !prevCursorWin.isExternal {
// w.s.ws.cursor.raise()
// }
// }
// if w.s.ws.cursor.isInPalette {
// w.s.ws.cursor.raise()
// }
}
w.s.ws.cursor.raise()
}
func (w *Window) show() {
w.fill()
w.Show()
// set buffer local ts value
if w.s.ws.ts != w.ts {
w.s.ws.optionsetMutex.Lock()
w.s.ws.ts = w.ts
w.s.ws.optionsetMutex.Unlock()
}
}
func (w *Window) hide() {
w.Hide()
}
func (w *Window) setGridGeometry(width, height int) {
if w.isExternal && !w.extwinResized {
w.extwin.Resize2(width+EXTWINBORDERSIZE*2, height+EXTWINBORDERSIZE*2)
}
w.extwinResized = false
rect := core.NewQRect4(0, 0, width, height)
w.SetGeometry(rect)
w.fill()
}
// refreshUpdateArea:: arg:0 => full, arg:1 => full only text
func (w *Window) refreshUpdateArea(fullmode int) {
var boundary int
if fullmode == 0 {
boundary = w.cols
} else {
boundary = w.maxLenContent
}
for i := 0; i < len(w.lenContent); i++ {
w.countContent(i)
w.lenContent[i] = boundary
for j, _ := range w.contentMask[i] {
w.contentMask[i][j] = true
}
}
}
func (w *Window) fill() {
w.refreshUpdateArea(0)
setAutoFill := true
if editor.config.Editor.EnableBackgroundBlur ||
editor.config.Editor.Transparent < 1.0 {
if !w.isExternal {
setAutoFill = false
}
} else {
if w.isMsgGrid && editor.config.Message.Transparent < 1.0 {
setAutoFill = false
} else if w.isPopupmenu && w.s.ws.pb > 0 {
setAutoFill = false
} else if w.isFloatWin && w.wb > 0 {
setAutoFill = false
}
}
if !setAutoFill {
return
}
if w.background != nil {
w.SetAutoFillBackground(true)
p := gui.NewQPalette()
p.SetColor2(gui.QPalette__Background, w.background.QColor())
w.SetPalette(p)
}
}
func (w *Window) setShadow() {
if !editor.config.Editor.DrawShadowForFloatWindow {
return
}
w.SetGraphicsEffect(util.DropShadow(0, 25, 125, 110))
}
func (w *Window) position() (int, int) {
pos := w.Pos()
posx := int(float64(pos.X()) / w.s.font.cellwidth)
posy := int(float64(pos.Y()) / float64(w.s.font.lineHeight))
return posx, posy
}
func (w *Window) move(col int, row int, anchorwindow ...*Window) {
font := w.s.font
var anchorwin *Window
if len(anchorwindow) > 0 {
anchorwin = anchorwindow[0]
if anchorwin != nil {
font = anchorwin.getFont()
}
}
x := int(float64(col) * font.cellwidth)
y := (row * font.lineHeight)
// Fix https://github.com/akiyosi/goneovim/issues/316#issuecomment-1039978355
// Adjustment of the float window position when the repositioning process
// is being applied to the anchor window when it is outside the application window.
var anchorposx, anchorposy int
if anchorwin != nil {
if anchorwin.grid != w.grid {
anchorposx = anchorwin.Pos().X()
anchorposy = anchorwin.Pos().Y()
}
}
if w.isFloatWin && !w.isMsgGrid {
// A workaround for ext_popupmenu and displaying a LSP tooltip
if editor.config.Editor.ExtPopupmenu {
if w.s.ws.mode == "insert" && w.s.ws.popup.widget.IsVisible() {
if w.s.ws.popup.widget.IsVisible() {
w.SetGraphicsEffect(util.DropShadow(0, 25, 125, 110))
w.Move2(
w.s.ws.popup.widget.X()+w.s.ws.popup.widget.Width()+5,
w.s.ws.popup.widget.Y(),
)
w.raise()
}
return
}
}
// #316
// Adjust the position of the floating window to the inside of the screen
// when it is outside of the screen.
x, y = w.repositioningFloatwindow([2]int{anchorposx + x, anchorposy + y})
}
if w.isExternal {
w.Move2(EXTWINBORDERSIZE, EXTWINBORDERSIZE)
w.layoutExternalWindow(x, y)
return
}
w.Move2(x, y)
}
func (w *Window) setFloatWindowPosition() {
wincols := int(float64(w.cols) * w.getFont().cellwidth / w.anchorwin.getFont().cellwidth)
winrows := int(math.Ceil(float64(w.rows*w.getFont().lineHeight) / float64(w.anchorwin.getFont().lineHeight)))
var col, row int
switch w.anchor {
case "NW":
col = w.anchorCol
row = w.anchorRow
case "NE":
col = w.anchorCol - wincols
row = w.anchorRow
case "SW":
col = w.anchorCol
row = w.anchorRow - winrows
case "SE":
col = w.anchorCol - w.cols
row = w.anchorRow - w.rows
}
w.updateMutex.Lock()
w.pos = [2]int{col, row}
w.updateMutex.Unlock()
}
func (w *Window) setOptions() {
if w.s == nil || w.s.ws == nil {
return
}
if w.id == 0 {
return
}
if editor.config.Editor.IndentGuide {
// get tabstop
w.ts = util.ReflectToInt(w.s.ws.getBufferOption(NVIMCALLTIMEOUT, "ts", w.id))
if w.ft == "" {
// get filetype
ftITF := w.s.ws.getBufferOption(NVIMCALLTIMEOUT, "ft", w.id)
ft, ok := ftITF.(string)
if ok {
w.ft = ft
}
}
}
}
func (w *Window) repositioningFloatwindow(pos ...[2]int) (int, int) {
baseFont := w.s.ws.screen.font
var winx, winy int
if len(pos) > 0 {
winx = pos[0][0]
winy = pos[0][1]
} else {
winx = w.Pos().X()
winy = w.Pos().Y()
}
if w.isMsgGrid {
return winx, winy
}
width := w.Width()
height := w.Height()
screenWidth := w.s.widget.Width()
screenHeight := w.s.widget.Height()
if float64((winx+width)-screenWidth) >= baseFont.cellwidth {
winx -= winx + width - screenWidth
}
if (winy+height)-screenHeight >= baseFont.lineHeight && !w.isPopupmenu {
winy -= winy + height - screenHeight
}
// If the position coordinate is a negative value, it is reset to zero.
if winx < 0 {
winx = 0
}
if winy < 0 && !w.isPopupmenu {
winy = 0
}
return winx, winy
}
func (w *Window) layoutExternalWindow(x, y int) {
font := w.s.font
// float windows width, height
width := int(float64(w.cols) * font.cellwidth)
height := w.rows * font.lineHeight
dx := []int{}
dy := []int{}
// layout external windows
// Adjacent to each other through edges of the same length as much as possible.
if w.pos[0] == 0 && w.pos[1] == 0 && !w.extwinManualResized {
w.s.windows.Range(func(_, winITF interface{}) bool {
win := winITF.(*Window)
if win == nil {
return true
}
if win.grid == 1 {
return true
}
if win.isMsgGrid {
return true
}
if win.grid == w.grid {
return true
}
if win.isExternal {
dc := 0
dr := 0
for _, e := range dx {
dc += e
}
for _, e := range dy {
dr += e
}
winx := 0
winy := 0
for _, e := range win.extwinAutoLayoutPosX {
winx += e
}
for _, e := range win.extwinAutoLayoutPosY {
winy += e
}
if winx <= w.pos[0]+dc && winx+win.cols > w.pos[0]+dc &&
winy <= w.pos[1]+dr && winy+win.rows > w.pos[1]+dr {
widthRatio := float64(w.cols+win.cols) * font.cellwidth / float64(editor.window.Width())
heightRatio := float64((w.rows+win.rows)*font.lineHeight) / float64(editor.window.Height())
if w.cols == win.cols {
dy = append(dy, win.rows)
height += win.rows*font.lineHeight + EXTWINBORDERSIZE*2 + EXTWINMARGINSIZE
} else if w.rows == win.rows {
dx = append(dx, win.cols)
width += int(float64(win.cols)*font.cellwidth) + EXTWINBORDERSIZE*2 + EXTWINMARGINSIZE
} else {
if widthRatio > heightRatio {
dy = append(dy, win.rows)
height += win.rows*font.lineHeight + EXTWINBORDERSIZE*2 + EXTWINMARGINSIZE
} else {
dx = append(dx, win.cols)
width += int(float64(win.cols)*font.cellwidth) + EXTWINBORDERSIZE*2 + EXTWINMARGINSIZE
}
}
return true
}
}
return true
})
x := 0
y := 0
for _, e := range dx {
x += int(float64(e)*font.cellwidth) + EXTWINBORDERSIZE*2 + EXTWINMARGINSIZE
}
for _, e := range dy {
y += e*font.lineHeight + EXTWINBORDERSIZE*2 + EXTWINMARGINSIZE
}
w.extwinAutoLayoutPosX = dx
w.extwinAutoLayoutPosY = dy
w.extwinRelativePos = [2]int{editor.window.Pos().X() + x, editor.window.Pos().Y() + y}
}
w.extwin.Move2(editor.window.Pos().X()+x, editor.window.Pos().Y()+y)
// centering
if w.pos[0] == 0 && w.pos[1] == 0 && !w.extwinManualResized {
var newx, newy int
if editor.window.Width()-width > 0 {
newx = int(float64(editor.window.Width()-width)/2.0) - (EXTWINMARGINSIZE / 2)
}
if editor.window.Height()-height > 0 {
newy = int(float64(editor.window.Height()-height)/2.0) - (EXTWINMARGINSIZE / 2)
}
w.s.windows.Range(func(_, winITF interface{}) bool {
win := winITF.(*Window)
if win == nil {
return true
}
if win.grid == 1 {
return true
}
if win.isMsgGrid {
return true
}
if win.isExternal && !win.extwinManualResized {
win.extwin.Move2(win.extwinRelativePos[0]+newx, win.extwinRelativePos[1]+newy)
}
return true
})
}
}
func (w *Window) combinePixmap(p1, p2 *gui.QPixmap, overlapping float64) *gui.QPixmap {
newHeight := float64(p1.Height()+p2.Height()) - overlapping
newpixmap := gui.NewQPixmap2(
core.NewQSize2(
int(w.devicePixelRatio*float64(p1.Width())),
int(w.devicePixelRatio*math.Ceil(newHeight)),
),
)
newpixmap.SetDevicePixelRatio(w.devicePixelRatio)
newpixmap.Fill(newRGBA(0, 0, 0, 0).QColor())
p := gui.NewQPainter2(newpixmap)
p.DrawPixmap9(
0,
0,
p1,
)
p.DrawPixmap9(
0,
int(float64(p1.Height())-overlapping),
p2,
)
p.End()
return newpixmap
}