mirror of https://github.com/liamg/aminal.git
Implementation of the vertical scrollbar (#229)
* Implementation of the vertical scrollbar (also enable gofmt checks only for go1.11.x builds)
This commit is contained in:
parent
000e763a61
commit
c2a7be2aeb
33
README.md
33
README.md
|
@ -114,28 +114,29 @@ shell = "/bin/bash" # The shell to run for the terminal session. Default
|
||||||
search_url = "https://www.google.com/search?q=$QUERY" # The search engine to use for the "search selected text" action. Defaults to google. Set this to your own search url using $QUERY as the keywords to replace when searching.
|
search_url = "https://www.google.com/search?q=$QUERY" # The search engine to use for the "search selected text" action. Defaults to google. Set this to your own search url using $QUERY as the keywords to replace when searching.
|
||||||
max_lines = 1000 # Maximum number of lines in the terminal buffer.
|
max_lines = 1000 # Maximum number of lines in the terminal buffer.
|
||||||
copy_and_paste_with_mouse = true # Text selected with the mouse is copied to the clipboard on end selection, and is pasted on right mouse button click.
|
copy_and_paste_with_mouse = true # Text selected with the mouse is copied to the clipboard on end selection, and is pasted on right mouse button click.
|
||||||
|
show_vertical_scrollbar = true # Whether to show the vertical scrollbar
|
||||||
dpi-scale = 0.0 # Override DPI scale. Defaults to 0.0 (let Aminal determine the DPI scale itself).
|
dpi-scale = 0.0 # Override DPI scale. Defaults to 0.0 (let Aminal determine the DPI scale itself).
|
||||||
|
|
||||||
[colours]
|
[colours]
|
||||||
cursor = "#e8dfd6"
|
cursor = "#e8dfd6"
|
||||||
foreground = "#e8dfd6"
|
foreground = "#e8dfd6"
|
||||||
background = "#021b21"
|
background = "#021b21"
|
||||||
black = "#032c36"
|
black = "#000000"
|
||||||
red = "#c2454e"
|
red = "#800000"
|
||||||
green = "#7cbf9e"
|
green = "#008000"
|
||||||
yellow = "#8a7a63"
|
yellow = "#808000"
|
||||||
blue = "#065f73"
|
blue = "#000080"
|
||||||
magenta = "#ff5879"
|
magenta = "#800080"
|
||||||
cyan = "#44b5b1"
|
cyan = "#008080"
|
||||||
light_grey = "#f2f1b9"
|
light_grey = "#f2f2f2"
|
||||||
dark_grey = "#3e4360"
|
dark_grey = "#808080"
|
||||||
light_red = "#ef5847"
|
light_red = "#ff0000"
|
||||||
light_green = "#a2db91"
|
light_green = "#00ff00"
|
||||||
light_yellow = "#beb090"
|
light_yellow = "#ffff00"
|
||||||
light_blue = "#61778d"
|
light_blue = "#0000ff"
|
||||||
light_magenta = "#ff99a1"
|
light_magenta = "#ff00ff"
|
||||||
light_cyan = "#9ed9d8"
|
light_cyan = "#00ffff"
|
||||||
white = "#f6f6c9"
|
white = "#ffffff"
|
||||||
selection = "#333366" # Mouse selection background colour
|
selection = "#333366" # Mouse selection background colour
|
||||||
|
|
||||||
[keys]
|
[keys]
|
||||||
|
|
|
@ -518,6 +518,15 @@ func (buffer *Buffer) convertRawLineToViewLine(rawLine uint64) uint16 {
|
||||||
return uint16(int(rawLine) - (rawHeight - int(buffer.terminalState.viewHeight)))
|
return uint16(int(rawLine) - (rawHeight - int(buffer.terminalState.viewHeight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) GetVPosition() int {
|
||||||
|
result := int(uint(buffer.Height()) - uint(buffer.ViewHeight()) - buffer.terminalState.scrollLinesFromBottom)
|
||||||
|
if result < 0 {
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Width returns the width of the buffer in columns
|
// Width returns the width of the buffer in columns
|
||||||
func (buffer *Buffer) Width() uint16 {
|
func (buffer *Buffer) Width() uint16 {
|
||||||
return buffer.terminalState.viewWidth
|
return buffer.terminalState.viewWidth
|
||||||
|
@ -545,7 +554,7 @@ func (buffer *Buffer) insertLine() {
|
||||||
|
|
||||||
if !buffer.InScrollableRegion() {
|
if !buffer.InScrollableRegion() {
|
||||||
pos := buffer.RawLine()
|
pos := buffer.RawLine()
|
||||||
maxLines := buffer.getMaxLines()
|
maxLines := buffer.GetMaxLines()
|
||||||
newLineCount := uint64(len(buffer.lines) + 1)
|
newLineCount := uint64(len(buffer.lines) + 1)
|
||||||
if newLineCount > maxLines {
|
if newLineCount > maxLines {
|
||||||
newLineCount = maxLines
|
newLineCount = maxLines
|
||||||
|
@ -641,7 +650,7 @@ func (buffer *Buffer) Index() {
|
||||||
|
|
||||||
if buffer.terminalState.cursorY >= buffer.ViewHeight()-1 {
|
if buffer.terminalState.cursorY >= buffer.ViewHeight()-1 {
|
||||||
buffer.lines = append(buffer.lines, newLine())
|
buffer.lines = append(buffer.lines, newLine())
|
||||||
maxLines := buffer.getMaxLines()
|
maxLines := buffer.GetMaxLines()
|
||||||
if uint64(len(buffer.lines)) > maxLines {
|
if uint64(len(buffer.lines)) > maxLines {
|
||||||
copy(buffer.lines, buffer.lines[uint64(len(buffer.lines))-maxLines:])
|
copy(buffer.lines, buffer.lines[uint64(len(buffer.lines))-maxLines:])
|
||||||
buffer.lines = buffer.lines[:maxLines]
|
buffer.lines = buffer.lines[:maxLines]
|
||||||
|
@ -1115,7 +1124,7 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
|
||||||
buffer.terminalState.ResetVerticalMargins()
|
buffer.terminalState.ResetVerticalMargins()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) getMaxLines() uint64 {
|
func (buffer *Buffer) GetMaxLines() uint64 {
|
||||||
result := buffer.terminalState.maxLines
|
result := buffer.terminalState.maxLines
|
||||||
if result < uint64(buffer.terminalState.viewHeight) {
|
if result < uint64(buffer.terminalState.viewHeight) {
|
||||||
result = uint64(buffer.terminalState.viewHeight)
|
result = uint64(buffer.terminalState.viewHeight)
|
||||||
|
|
|
@ -14,6 +14,7 @@ type Config struct {
|
||||||
SearchURL string `toml:"search_url"`
|
SearchURL string `toml:"search_url"`
|
||||||
MaxLines uint64 `toml:"max_lines"`
|
MaxLines uint64 `toml:"max_lines"`
|
||||||
CopyAndPasteWithMouse bool `toml:"copy_and_paste_with_mouse"`
|
CopyAndPasteWithMouse bool `toml:"copy_and_paste_with_mouse"`
|
||||||
|
ShowVerticalScrollbar bool `toml:"show_vertical_scrollbar"`
|
||||||
|
|
||||||
// Developer options.
|
// Developer options.
|
||||||
DebugMode bool `toml:"debug"`
|
DebugMode bool `toml:"debug"`
|
||||||
|
|
|
@ -39,6 +39,7 @@ func DefaultConfig() *Config {
|
||||||
SearchURL: "https://www.google.com/search?q=$QUERY",
|
SearchURL: "https://www.google.com/search?q=$QUERY",
|
||||||
MaxLines: 1000,
|
MaxLines: 1000,
|
||||||
CopyAndPasteWithMouse: true,
|
CopyAndPasteWithMouse: true,
|
||||||
|
ShowVerticalScrollbar: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
gui/fonts.go
10
gui/fonts.go
|
@ -8,14 +8,14 @@ import (
|
||||||
"github.com/liamg/aminal/glfont"
|
"github.com/liamg/aminal/glfont"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *GUI) getPackedFont(name string) (*glfont.Font, error) {
|
func (gui *GUI) getPackedFont(name string, actualWidth int, actualHeight int) (*glfont.Font, error) {
|
||||||
box := packr.NewBox("./packed-fonts")
|
box := packr.NewBox("./packed-fonts")
|
||||||
fontBytes, err := box.Find(name)
|
fontBytes, err := box.Find(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("packaged font '%s' could not be read: %s", name, err)
|
return nil, fmt.Errorf("packaged font '%s' could not be read: %s", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
font, err := glfont.LoadFont(bytes.NewReader(fontBytes), gui.fontScale*gui.dpiScale/gui.scale(), gui.width, gui.height)
|
font, err := glfont.LoadFont(bytes.NewReader(fontBytes), gui.fontScale*gui.dpiScale/gui.scale(), actualWidth, actualHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("font '%s' failed to load: %v", name, err)
|
return nil, fmt.Errorf("font '%s' failed to load: %v", name, err)
|
||||||
}
|
}
|
||||||
|
@ -23,16 +23,16 @@ func (gui *GUI) getPackedFont(name string) (*glfont.Font, error) {
|
||||||
return font, nil
|
return font, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) loadFonts() error {
|
func (gui *GUI) loadFonts(actualWidth int, actualHeight int) error {
|
||||||
|
|
||||||
// from https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/Hack
|
// from https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/Hack
|
||||||
|
|
||||||
defaultFont, err := gui.getPackedFont("Hack Regular Nerd Font Complete.ttf")
|
defaultFont, err := gui.getPackedFont("Hack Regular Nerd Font Complete.ttf", actualWidth, actualHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
boldFont, err := gui.getPackedFont("Hack Bold Nerd Font Complete.ttf")
|
boldFont, err := gui.getPackedFont("Hack Bold Nerd Font Complete.ttf", actualWidth, actualHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
125
gui/gui.go
125
gui/gui.go
|
@ -30,6 +30,18 @@ import (
|
||||||
const wakePeriod = time.Second / 120
|
const wakePeriod = time.Second / 120
|
||||||
const halfWakePeriod = wakePeriod / 2
|
const halfWakePeriod = wakePeriod / 2
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultWindowWidth = 800
|
||||||
|
DefaultWindowHeight = 600
|
||||||
|
)
|
||||||
|
|
||||||
|
type mouseEventsHandler interface {
|
||||||
|
mouseMoveCallback(g *GUI, px float64, py float64)
|
||||||
|
mouseButtonCallback(g *GUI, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey, mouseX float64, mouseY float64)
|
||||||
|
cursorEnterCallback(g *GUI, enter bool)
|
||||||
|
isMouseInside(px float64, py float64) bool
|
||||||
|
}
|
||||||
|
|
||||||
type GUI struct {
|
type GUI struct {
|
||||||
window *glfw.Window
|
window *glfw.Window
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
|
@ -63,8 +75,15 @@ type GUI struct {
|
||||||
leftClickTime time.Time
|
leftClickTime time.Time
|
||||||
leftClickCount int // number of clicks in a serie - single click, double click, or triple click
|
leftClickCount int // number of clicks in a serie - single click, double click, or triple click
|
||||||
mouseMovedAfterSelectionStarted bool
|
mouseMovedAfterSelectionStarted bool
|
||||||
internalResize bool
|
|
||||||
selectionRegionMode buffer.SelectionRegionMode
|
catchedMouseHandler mouseEventsHandler
|
||||||
|
mouseCatchedOnButton glfw.MouseButton
|
||||||
|
prevMouseEventHandler mouseEventsHandler
|
||||||
|
|
||||||
|
internalResize bool
|
||||||
|
selectionRegionMode buffer.SelectionRegionMode
|
||||||
|
|
||||||
|
vScrollbar *scrollbar
|
||||||
|
|
||||||
mainThreadFunc chan func()
|
mainThreadFunc chan func()
|
||||||
}
|
}
|
||||||
|
@ -156,24 +175,33 @@ func New(config *config.Config, terminal *terminal.Terminal, logger *zap.Sugared
|
||||||
}
|
}
|
||||||
|
|
||||||
return &GUI{
|
return &GUI{
|
||||||
config: config,
|
config: config,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
width: 800,
|
width: DefaultWindowWidth,
|
||||||
height: 600,
|
height: DefaultWindowHeight,
|
||||||
appliedWidth: 0,
|
appliedWidth: 0,
|
||||||
appliedHeight: 0,
|
appliedHeight: 0,
|
||||||
dpiScale: 1,
|
dpiScale: 1,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
fontScale: 10.0,
|
fontScale: 10.0,
|
||||||
terminalAlpha: 1,
|
terminalAlpha: 1,
|
||||||
keyboardShortcuts: shortcuts,
|
keyboardShortcuts: shortcuts,
|
||||||
resizeLock: &sync.Mutex{},
|
resizeLock: &sync.Mutex{},
|
||||||
internalResize: false,
|
internalResize: false,
|
||||||
|
vScrollbar: nil,
|
||||||
|
catchedMouseHandler: nil,
|
||||||
|
|
||||||
mainThreadFunc: make(chan func()),
|
mainThreadFunc: make(chan func()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) Free() {
|
||||||
|
if gui.vScrollbar != nil {
|
||||||
|
gui.vScrollbar.Free()
|
||||||
|
gui.vScrollbar = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// inspired by https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
// inspired by https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||||
|
|
||||||
func (gui *GUI) scale() float32 {
|
func (gui *GUI) scale() float32 {
|
||||||
|
@ -208,15 +236,20 @@ func (gui *GUI) resizeToTerminal() {
|
||||||
gui.logger.Debugf("Initiating GUI resize to columns=%d rows=%d", newCols, newRows)
|
gui.logger.Debugf("Initiating GUI resize to columns=%d rows=%d", newCols, newRows)
|
||||||
|
|
||||||
gui.logger.Debugf("Calculating size...")
|
gui.logger.Debugf("Calculating size...")
|
||||||
width, height := gui.renderer.GetRectangleSize(newCols, newRows)
|
width, height := gui.renderer.ConvertCoordinates(newCols, newRows)
|
||||||
|
|
||||||
roundedWidth := int(math.Ceil(float64(width)))
|
roundedWidth := int(math.Ceil(float64(width)))
|
||||||
roundedHeight := int(math.Ceil(float64(height)))
|
roundedHeight := int(math.Ceil(float64(height)))
|
||||||
|
|
||||||
|
if gui.vScrollbar != nil {
|
||||||
|
roundedWidth += int(gui.vScrollbar.position.width())
|
||||||
|
}
|
||||||
|
|
||||||
gui.resizeCache = &ResizeCache{roundedWidth, roundedHeight, newCols, newRows}
|
gui.resizeCache = &ResizeCache{roundedWidth, roundedHeight, newCols, newRows}
|
||||||
|
|
||||||
gui.logger.Debugf("Resizing window to %dx%d", roundedWidth, roundedHeight)
|
gui.logger.Debugf("Resizing window to %dx%d", roundedWidth, roundedHeight)
|
||||||
gui.internalResize = true
|
gui.internalResize = true
|
||||||
|
|
||||||
gui.window.SetSize(roundedWidth, roundedHeight) // will trigger resize()
|
gui.window.SetSize(roundedWidth, roundedHeight) // will trigger resize()
|
||||||
gui.internalResize = false
|
gui.internalResize = false
|
||||||
}
|
}
|
||||||
|
@ -278,14 +311,20 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
|
|
||||||
gui.width = width
|
gui.width = width
|
||||||
gui.height = height
|
gui.height = height
|
||||||
gui.appliedWidth = width
|
gui.appliedWidth = gui.width
|
||||||
gui.appliedHeight = height
|
gui.appliedHeight = gui.height
|
||||||
|
|
||||||
|
vScrollbarWidth := 0
|
||||||
|
if gui.vScrollbar != nil {
|
||||||
|
gui.vScrollbar.resize(gui)
|
||||||
|
vScrollbarWidth = int(gui.vScrollbar.position.width())
|
||||||
|
}
|
||||||
|
|
||||||
gui.logger.Debugf("Updating font resolutions...")
|
gui.logger.Debugf("Updating font resolutions...")
|
||||||
gui.loadFonts()
|
gui.loadFonts(gui.width, gui.height)
|
||||||
|
|
||||||
gui.logger.Debugf("Setting renderer area...")
|
gui.logger.Debugf("Setting renderer area...")
|
||||||
gui.renderer.SetArea(0, 0, gui.width, gui.height)
|
gui.renderer.SetArea(0, 0, gui.width-vScrollbarWidth, gui.height)
|
||||||
|
|
||||||
if gui.resizeCache != nil && gui.resizeCache.Width == width && gui.resizeCache.Height == height {
|
if gui.resizeCache != nil && gui.resizeCache.Width == width && gui.resizeCache.Height == height {
|
||||||
gui.logger.Debugf("No need to resize internal terminal!")
|
gui.logger.Debugf("No need to resize internal terminal!")
|
||||||
|
@ -338,14 +377,14 @@ func (gui *GUI) Render() error {
|
||||||
gui.logger.Debugf("Creating window...")
|
gui.logger.Debugf("Creating window...")
|
||||||
var err error
|
var err error
|
||||||
gui.window, err = gui.createWindow()
|
gui.window, err = gui.createWindow()
|
||||||
gui.SetDPIScale()
|
|
||||||
gui.window.SetSize(int(float32(gui.width)*gui.dpiScale),
|
|
||||||
int(float32(gui.height)*gui.dpiScale))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to create window: %s", err)
|
return fmt.Errorf("Failed to create window: %s", err)
|
||||||
}
|
}
|
||||||
defer glfw.Terminate()
|
defer glfw.Terminate()
|
||||||
|
|
||||||
|
gui.SetDPIScale()
|
||||||
|
gui.window.SetSize(int(float32(gui.width)*gui.dpiScale), int(float32(gui.height)*gui.dpiScale))
|
||||||
|
|
||||||
gui.logger.Debugf("Initialising OpenGL and creating program...")
|
gui.logger.Debugf("Initialising OpenGL and creating program...")
|
||||||
program, err := gui.createProgram()
|
program, err := gui.createProgram()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -355,8 +394,19 @@ func (gui *GUI) Render() error {
|
||||||
gui.colourAttr = uint32(gl.GetAttribLocation(program, gl.Str("inColour\x00")))
|
gui.colourAttr = uint32(gl.GetAttribLocation(program, gl.Str("inColour\x00")))
|
||||||
gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00"))
|
gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00"))
|
||||||
|
|
||||||
|
vScrollbarWidth := 0
|
||||||
|
if gui.config.ShowVerticalScrollbar {
|
||||||
|
vScrollbar, err := newScrollbar()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gui.vScrollbar = vScrollbar
|
||||||
|
gui.vScrollbar.resize(gui)
|
||||||
|
vScrollbarWidth = int(gui.vScrollbar.position.width())
|
||||||
|
}
|
||||||
|
|
||||||
gui.logger.Debugf("Loading font...")
|
gui.logger.Debugf("Loading font...")
|
||||||
if err := gui.loadFonts(); err != nil {
|
if err := gui.loadFonts(gui.width, gui.height); err != nil {
|
||||||
return fmt.Errorf("Failed to load font: %s", err)
|
return fmt.Errorf("Failed to load font: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,14 +414,18 @@ func (gui *GUI) Render() error {
|
||||||
resizeChan := make(chan bool, 1)
|
resizeChan := make(chan bool, 1)
|
||||||
reverseChan := make(chan bool, 1)
|
reverseChan := make(chan bool, 1)
|
||||||
|
|
||||||
gui.renderer = NewOpenGLRenderer(gui.config, gui.fontMap, 0, 0, gui.width, gui.height, gui.colourAttr, program)
|
gui.renderer, err = NewOpenGLRenderer(gui.config, gui.fontMap, 0, 0, gui.width-vScrollbarWidth, gui.height, gui.colourAttr, program)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
gui.window.SetFramebufferSizeCallback(gui.resize)
|
gui.window.SetFramebufferSizeCallback(gui.resize)
|
||||||
gui.window.SetKeyCallback(gui.key)
|
gui.window.SetKeyCallback(gui.key)
|
||||||
gui.window.SetCharCallback(gui.char)
|
gui.window.SetCharCallback(gui.char)
|
||||||
gui.window.SetScrollCallback(gui.glfwScrollCallback)
|
gui.window.SetScrollCallback(gui.glfwScrollCallback)
|
||||||
gui.window.SetMouseButtonCallback(gui.mouseButtonCallback)
|
gui.window.SetMouseButtonCallback(gui.globalMouseButtonCallback)
|
||||||
gui.window.SetCursorPosCallback(gui.mouseMoveCallback)
|
gui.window.SetCursorPosCallback(gui.globalMouseMoveCallback)
|
||||||
|
gui.window.SetCursorEnterCallback(gui.globalCursorEnterCallback)
|
||||||
gui.window.SetRefreshCallback(func(w *glfw.Window) {
|
gui.window.SetRefreshCallback(func(w *glfw.Window) {
|
||||||
gui.terminal.NotifyDirty()
|
gui.terminal.NotifyDirty()
|
||||||
})
|
})
|
||||||
|
@ -404,6 +458,10 @@ func (gui *GUI) Render() error {
|
||||||
gui.Close()
|
gui.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if gui.vScrollbar != nil {
|
||||||
|
gui.vScrollbar.resize(gui)
|
||||||
|
}
|
||||||
|
|
||||||
gui.logger.Debugf("Starting render...")
|
gui.logger.Debugf("Starting render...")
|
||||||
|
|
||||||
gl.UseProgram(program)
|
gl.UseProgram(program)
|
||||||
|
@ -677,8 +735,9 @@ func (gui *GUI) renderTerminalData(shouldLock bool) {
|
||||||
gui.renderer.DrawUnderline(span, uint(x-span), uint(y), colour)
|
gui.renderer.DrawUnderline(span, uint(x-span), uint(y), colour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.renderScrollbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) redraw(shouldLock bool) {
|
func (gui *GUI) redraw(shouldLock bool) {
|
||||||
|
@ -832,6 +891,16 @@ func (gui *GUI) monitorChangeCallback(monitor *glfw.Monitor, event glfw.MonitorE
|
||||||
gui.SetDPIScale()
|
gui.SetDPIScale()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) renderScrollbar() {
|
||||||
|
if gui.vScrollbar != nil {
|
||||||
|
position := gui.terminal.ActiveBuffer().GetVPosition()
|
||||||
|
maxPosition := int(gui.terminal.ActiveBuffer().GetMaxLines()) - int(gui.terminal.ActiveBuffer().ViewHeight())
|
||||||
|
|
||||||
|
gui.vScrollbar.setPosition(maxPosition, position)
|
||||||
|
gui.vScrollbar.render(gui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Synchronously executes the argument function in the main thread.
|
// Synchronously executes the argument function in the main thread.
|
||||||
// Does not return until f() executed!
|
// Does not return until f() executed!
|
||||||
func (gui *GUI) executeInMainThread(f func() error) error {
|
func (gui *GUI) executeInMainThread(f func() error) error {
|
||||||
|
|
95
gui/mouse.go
95
gui/mouse.go
|
@ -36,7 +36,86 @@ func (gui *GUI) getArrowCursor() *glfw.Cursor {
|
||||||
return gui.arrowCursor
|
return gui.arrowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) mouseMoveCallback(w *glfw.Window, px float64, py float64) {
|
func (gui *GUI) scaleMouseCoordinates(px float64, py float64) (float64, float64) {
|
||||||
|
scale := float64(gui.scale())
|
||||||
|
px = px / scale
|
||||||
|
py = py / scale
|
||||||
|
|
||||||
|
return px, py
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) globalMouseMoveCallback(w *glfw.Window, px float64, py float64) {
|
||||||
|
px, py = gui.scaleMouseCoordinates(px, py)
|
||||||
|
|
||||||
|
if gui.catchedMouseHandler != nil {
|
||||||
|
gui.catchedMouseHandler.mouseMoveCallback(gui, px, py)
|
||||||
|
} else {
|
||||||
|
if gui.isMouseInside(px, py) {
|
||||||
|
if gui.prevMouseEventHandler != gui {
|
||||||
|
if gui.prevMouseEventHandler != nil {
|
||||||
|
gui.prevMouseEventHandler.cursorEnterCallback(gui, false)
|
||||||
|
}
|
||||||
|
gui.cursorEnterCallback(gui, true)
|
||||||
|
}
|
||||||
|
gui.mouseMoveCallback(gui, px, py)
|
||||||
|
gui.prevMouseEventHandler = gui
|
||||||
|
} else if gui.vScrollbar != nil && gui.vScrollbar.isMouseInside(px, py) {
|
||||||
|
if gui.prevMouseEventHandler != gui.vScrollbar {
|
||||||
|
if gui.prevMouseEventHandler != nil {
|
||||||
|
gui.prevMouseEventHandler.cursorEnterCallback(gui, false)
|
||||||
|
}
|
||||||
|
gui.vScrollbar.cursorEnterCallback(gui, true)
|
||||||
|
}
|
||||||
|
gui.vScrollbar.mouseMoveCallback(gui, px, py)
|
||||||
|
gui.prevMouseEventHandler = gui.vScrollbar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) globalMouseButtonCallback(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
|
||||||
|
mouseX, mouseY := gui.scaleMouseCoordinates(w.GetCursorPos())
|
||||||
|
|
||||||
|
if gui.catchedMouseHandler != nil {
|
||||||
|
gui.catchedMouseHandler.mouseButtonCallback(gui, button, action, mod, mouseX, mouseY)
|
||||||
|
if action == glfw.Release && button == gui.mouseCatchedOnButton {
|
||||||
|
gui.catchMouse(nil, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if gui.isMouseInside(mouseX, mouseY) {
|
||||||
|
if action == glfw.Press {
|
||||||
|
gui.catchMouse(gui, button)
|
||||||
|
}
|
||||||
|
gui.mouseButtonCallback(gui, button, action, mod, mouseX, mouseY)
|
||||||
|
} else if gui.vScrollbar != nil && gui.vScrollbar.isMouseInside(mouseX, mouseY) {
|
||||||
|
if action == glfw.Press {
|
||||||
|
gui.catchMouse(gui.vScrollbar, button)
|
||||||
|
}
|
||||||
|
gui.vScrollbar.mouseButtonCallback(gui, button, action, mod, mouseX, mouseY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) globalCursorEnterCallback(w *glfw.Window, entered bool) {
|
||||||
|
if !entered {
|
||||||
|
if gui.prevMouseEventHandler != nil {
|
||||||
|
gui.prevMouseEventHandler.cursorEnterCallback(gui, false)
|
||||||
|
gui.prevMouseEventHandler = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) catchMouse(newHandler mouseEventsHandler, button glfw.MouseButton) {
|
||||||
|
gui.catchedMouseHandler = newHandler
|
||||||
|
gui.mouseCatchedOnButton = button
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) isMouseInside(px float64, py float64) bool {
|
||||||
|
return px >= float64(gui.renderer.areaX) && px < float64(gui.renderer.areaX+gui.renderer.areaWidth) &&
|
||||||
|
py >= float64(gui.renderer.areaY) && py < float64(gui.renderer.areaY+gui.renderer.areaHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) mouseMoveCallback(g *GUI, px float64, py float64) {
|
||||||
|
|
||||||
x, y := gui.convertMouseCoordinates(px, py)
|
x, y := gui.convertMouseCoordinates(px, py)
|
||||||
|
|
||||||
|
@ -59,16 +138,13 @@ func (gui *GUI) mouseMoveCallback(w *glfw.Window, px float64, py float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
|
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
|
||||||
w.SetCursor(gui.getHandCursor())
|
gui.window.SetCursor(gui.getHandCursor())
|
||||||
} else {
|
} else {
|
||||||
w.SetCursor(gui.getArrowCursor())
|
gui.window.SetCursor(gui.getArrowCursor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) convertMouseCoordinates(px float64, py float64) (uint16, uint16) {
|
func (gui *GUI) convertMouseCoordinates(px float64, py float64) (uint16, uint16) {
|
||||||
scale := gui.scale()
|
|
||||||
px = px / float64(scale)
|
|
||||||
py = py / float64(scale)
|
|
||||||
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
|
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
|
||||||
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))
|
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))
|
||||||
|
|
||||||
|
@ -122,7 +198,7 @@ func btnCode(button glfw.MouseButton, release bool, mod glfw.ModifierKey) (b byt
|
||||||
return b, true
|
return b, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
|
func (gui *GUI) mouseButtonCallback(g *GUI, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey, mouseX float64, mouseY float64) {
|
||||||
|
|
||||||
if gui.overlay != nil {
|
if gui.overlay != nil {
|
||||||
if button == glfw.MouseButtonRight && action == glfw.Release {
|
if button == glfw.MouseButtonRight && action == glfw.Release {
|
||||||
|
@ -132,7 +208,7 @@ func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, act
|
||||||
}
|
}
|
||||||
|
|
||||||
// before we forward clicks on (below), we need to handle them locally for url clicking, text highlighting etc.
|
// before we forward clicks on (below), we need to handle them locally for url clicking, text highlighting etc.
|
||||||
x, y := gui.convertMouseCoordinates(w.GetCursorPos())
|
x, y := gui.convertMouseCoordinates(mouseX, mouseY)
|
||||||
tx := int(x) + 1 // vt100 is 1 indexed
|
tx := int(x) + 1 // vt100 is 1 indexed
|
||||||
ty := int(y) + 1
|
ty := int(y) + 1
|
||||||
|
|
||||||
|
@ -252,7 +328,10 @@ func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, act
|
||||||
default:
|
default:
|
||||||
panic("Unsupported mouse mode")
|
panic("Unsupported mouse mode")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *GUI) cursorEnterCallback(g *GUI, entered bool) {
|
||||||
|
// empty, just to conform to the mouseEventsHandler interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) handleSelectionButtonPress(x uint16, y uint16, mod glfw.ModifierKey) {
|
func (gui *GUI) handleSelectionButtonPress(x uint16, y uint16, mod glfw.ModifierKey) {
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-gl/gl/all-core/gl"
|
||||||
|
"github.com/liamg/aminal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rectangleRendererVertexShaderSource = `
|
||||||
|
#version 330 core
|
||||||
|
layout (location = 0) in vec2 position;
|
||||||
|
uniform vec2 resolution;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// convert from window coordinates to GL coordinates
|
||||||
|
vec2 glCoordinates = ((position / resolution) * 2.0 - 1.0) * vec2(1, -1);
|
||||||
|
|
||||||
|
gl_Position = vec4(glCoordinates, 0.0, 1.0);
|
||||||
|
}` + "\x00"
|
||||||
|
|
||||||
|
rectangleRendererFragmentShaderSource = `
|
||||||
|
#version 330 core
|
||||||
|
uniform vec4 inColor;
|
||||||
|
out vec4 outColor;
|
||||||
|
void main() {
|
||||||
|
outColor = inColor;
|
||||||
|
}` + "\x00"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rectangleRenderer struct {
|
||||||
|
program uint32
|
||||||
|
vbo uint32
|
||||||
|
vao uint32
|
||||||
|
ibo uint32
|
||||||
|
uniformLocationResolution int32
|
||||||
|
uniformLocationInColor int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRectangleRendererProgram() (uint32, error) {
|
||||||
|
vertexShader, err := compileShader(rectangleRendererVertexShaderSource, gl.VERTEX_SHADER)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer gl.DeleteShader(vertexShader)
|
||||||
|
|
||||||
|
fragmentShader, err := compileShader(rectangleRendererFragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer gl.DeleteShader(fragmentShader)
|
||||||
|
|
||||||
|
prog := gl.CreateProgram()
|
||||||
|
gl.AttachShader(prog, vertexShader)
|
||||||
|
gl.AttachShader(prog, fragmentShader)
|
||||||
|
gl.LinkProgram(prog)
|
||||||
|
|
||||||
|
return prog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRectangleRenderer() (*rectangleRenderer, error) {
|
||||||
|
prog, err := createRectangleRendererProgram()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vbo uint32
|
||||||
|
var vao uint32
|
||||||
|
var ibo uint32
|
||||||
|
|
||||||
|
gl.GenBuffers(1, &vbo)
|
||||||
|
gl.GenVertexArrays(1, &vao)
|
||||||
|
gl.GenBuffers(1, &ibo)
|
||||||
|
|
||||||
|
indices := [...]uint32{
|
||||||
|
0, 1, 2,
|
||||||
|
2, 3, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.BindVertexArray(vao)
|
||||||
|
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||||
|
gl.BufferData(gl.ARRAY_BUFFER, 8*4, nil, gl.DYNAMIC_DRAW) // just reserve data for the buffer
|
||||||
|
|
||||||
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo)
|
||||||
|
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*4, gl.Ptr(&indices[0]), gl.DYNAMIC_DRAW)
|
||||||
|
|
||||||
|
gl.VertexAttribPointer(0, 2, gl.FLOAT, false, 2*4, nil)
|
||||||
|
gl.EnableVertexAttribArray(0)
|
||||||
|
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||||
|
gl.BindVertexArray(0)
|
||||||
|
|
||||||
|
return &rectangleRenderer{
|
||||||
|
program: prog,
|
||||||
|
vbo: vbo,
|
||||||
|
vao: vao,
|
||||||
|
ibo: ibo,
|
||||||
|
uniformLocationResolution: gl.GetUniformLocation(prog, gl.Str("resolution\x00")),
|
||||||
|
uniformLocationInColor: gl.GetUniformLocation(prog, gl.Str("inColor\x00")),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *rectangleRenderer) Free() {
|
||||||
|
if rr.program != 0 {
|
||||||
|
gl.DeleteProgram(rr.program)
|
||||||
|
rr.program = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.vbo != 0 {
|
||||||
|
gl.DeleteBuffers(1, &rr.vbo)
|
||||||
|
rr.vbo = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.vao != 0 {
|
||||||
|
gl.DeleteBuffers(1, &rr.vao)
|
||||||
|
rr.vao = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr.ibo != 0 {
|
||||||
|
gl.DeleteBuffers(1, &rr.ibo)
|
||||||
|
rr.ibo = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *rectangleRenderer) render(left float32, top float32, width float32, height float32, colour config.Colour) {
|
||||||
|
var savedProgram int32
|
||||||
|
gl.GetIntegerv(gl.CURRENT_PROGRAM, &savedProgram)
|
||||||
|
defer gl.UseProgram(uint32(savedProgram))
|
||||||
|
|
||||||
|
currentViewport := [4]int32{}
|
||||||
|
gl.GetIntegerv(gl.VIEWPORT, ¤tViewport[0])
|
||||||
|
|
||||||
|
gl.UseProgram(rr.program)
|
||||||
|
gl.Uniform2f(rr.uniformLocationResolution, float32(currentViewport[2]), float32(currentViewport[3]))
|
||||||
|
|
||||||
|
gl.Uniform4f(rr.uniformLocationInColor, colour[0], colour[1], colour[2], 1.0)
|
||||||
|
|
||||||
|
vertices := [...]float32{
|
||||||
|
left, top,
|
||||||
|
left + width, top,
|
||||||
|
left + width, top + height,
|
||||||
|
left, top + height,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
gl.NamedBufferSubData(rr.vbo, 0, len(vertices)*4, gl.Ptr(&vertices[0]))
|
||||||
|
/*/
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, rr.vbo)
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(vertices)*4, gl.Ptr(&vertices[0]))
|
||||||
|
//*/
|
||||||
|
|
||||||
|
gl.BindVertexArray(rr.vao)
|
||||||
|
|
||||||
|
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0))
|
||||||
|
|
||||||
|
gl.BindVertexArray(0)
|
||||||
|
}
|
150
gui/renderer.go
150
gui/renderer.go
|
@ -1,13 +1,12 @@
|
||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/go-gl/gl/all-core/gl"
|
"github.com/go-gl/gl/all-core/gl"
|
||||||
"github.com/liamg/aminal/buffer"
|
"github.com/liamg/aminal/buffer"
|
||||||
"github.com/liamg/aminal/config"
|
"github.com/liamg/aminal/config"
|
||||||
"github.com/liamg/aminal/glfont"
|
"github.com/liamg/aminal/glfont"
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenGLRenderer struct {
|
type OpenGLRenderer struct {
|
||||||
|
@ -26,16 +25,8 @@ type OpenGLRenderer struct {
|
||||||
textureMap map[*image.RGBA]uint32
|
textureMap map[*image.RGBA]uint32
|
||||||
fontMap *FontMap
|
fontMap *FontMap
|
||||||
backgroundColour [3]float32
|
backgroundColour [3]float32
|
||||||
}
|
|
||||||
|
|
||||||
type rectangle struct {
|
rectRenderer *rectangleRenderer
|
||||||
vao uint32
|
|
||||||
vbo uint32
|
|
||||||
cv uint32
|
|
||||||
colourAttr uint32
|
|
||||||
colour [3]float32
|
|
||||||
points [18]float32
|
|
||||||
prog uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) CellWidth() float32 {
|
func (r *OpenGLRenderer) CellWidth() float32 {
|
||||||
|
@ -46,95 +37,12 @@ func (r *OpenGLRenderer) CellHeight() float32 {
|
||||||
return r.cellHeight
|
return r.cellHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) newRectangleEx(x float32, y float32, width float32, height float32, colourAttr uint32) *rectangle {
|
func NewOpenGLRenderer(config *config.Config, fontMap *FontMap, areaX int, areaY int, areaWidth int, areaHeight int, colourAttr uint32, program uint32) (*OpenGLRenderer, error) {
|
||||||
|
rectRenderer, err := newRectangleRenderer()
|
||||||
rect := &rectangle{}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
halfAreaWidth := float32(r.areaWidth / 2)
|
|
||||||
halfAreaHeight := float32(r.areaHeight / 2)
|
|
||||||
|
|
||||||
x = (x - halfAreaWidth) / halfAreaWidth
|
|
||||||
y = -(y - (halfAreaHeight)) / halfAreaHeight
|
|
||||||
w := width / halfAreaWidth
|
|
||||||
h := height / halfAreaHeight
|
|
||||||
|
|
||||||
rect.points = [18]float32{
|
|
||||||
x, y, 0,
|
|
||||||
x, y + h, 0,
|
|
||||||
x + w, y + h, 0,
|
|
||||||
|
|
||||||
x + w, y, 0,
|
|
||||||
x, y, 0,
|
|
||||||
x + w, y + h, 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rect.colourAttr = colourAttr
|
|
||||||
rect.prog = r.program
|
|
||||||
|
|
||||||
// SHAPE
|
|
||||||
gl.GenBuffers(1, &rect.vbo)
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, rect.vbo)
|
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, 4*len(rect.points), gl.Ptr(&rect.points[0]), gl.STATIC_DRAW)
|
|
||||||
|
|
||||||
gl.GenVertexArrays(1, &rect.vao)
|
|
||||||
gl.BindVertexArray(rect.vao)
|
|
||||||
gl.EnableVertexAttribArray(0)
|
|
||||||
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, rect.vbo)
|
|
||||||
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
|
|
||||||
|
|
||||||
// colour
|
|
||||||
gl.GenBuffers(1, &rect.cv)
|
|
||||||
|
|
||||||
rect.setColour([3]float32{0, 1, 0})
|
|
||||||
|
|
||||||
return rect
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *rectangle {
|
|
||||||
return r.newRectangleEx(x, y, r.cellWidth, r.cellHeight, colourAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rect *rectangle) Draw() {
|
|
||||||
gl.UseProgram(rect.prog)
|
|
||||||
gl.BindVertexArray(rect.vao)
|
|
||||||
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rect *rectangle) setColour(colour [3]float32) {
|
|
||||||
if rect.colour == colour {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := []float32{
|
|
||||||
colour[0], colour[1], colour[2],
|
|
||||||
colour[0], colour[1], colour[2],
|
|
||||||
colour[0], colour[1], colour[2],
|
|
||||||
colour[0], colour[1], colour[2],
|
|
||||||
colour[0], colour[1], colour[2],
|
|
||||||
colour[0], colour[1], colour[2],
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.UseProgram(rect.prog)
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, rect.cv)
|
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, len(c)*4, gl.Ptr(c), gl.STATIC_DRAW)
|
|
||||||
gl.EnableVertexAttribArray(rect.colourAttr)
|
|
||||||
gl.VertexAttribPointer(rect.colourAttr, 3, gl.FLOAT, false, 0, gl.PtrOffset(0))
|
|
||||||
|
|
||||||
rect.colour = colour
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rect *rectangle) Free() {
|
|
||||||
gl.DeleteVertexArrays(1, &rect.vao)
|
|
||||||
gl.DeleteBuffers(1, &rect.vbo)
|
|
||||||
gl.DeleteBuffers(1, &rect.cv)
|
|
||||||
|
|
||||||
rect.vao = 0
|
|
||||||
rect.vbo = 0
|
|
||||||
rect.cv = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenGLRenderer(config *config.Config, fontMap *FontMap, areaX int, areaY int, areaWidth int, areaHeight int, colourAttr uint32, program uint32) *OpenGLRenderer {
|
|
||||||
r := &OpenGLRenderer{
|
r := &OpenGLRenderer{
|
||||||
areaWidth: areaWidth,
|
areaWidth: areaWidth,
|
||||||
areaHeight: areaHeight,
|
areaHeight: areaHeight,
|
||||||
|
@ -146,9 +54,10 @@ func NewOpenGLRenderer(config *config.Config, fontMap *FontMap, areaX int, areaY
|
||||||
program: program,
|
program: program,
|
||||||
textureMap: map[*image.RGBA]uint32{},
|
textureMap: map[*image.RGBA]uint32{},
|
||||||
fontMap: fontMap,
|
fontMap: fontMap,
|
||||||
|
rectRenderer: rectRenderer,
|
||||||
}
|
}
|
||||||
r.SetArea(areaX, areaY, areaWidth, areaHeight)
|
r.SetArea(areaX, areaY, areaWidth, areaHeight)
|
||||||
return r
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method ensures that all OpenGL resources are deleted correctly
|
// This method ensures that all OpenGL resources are deleted correctly
|
||||||
|
@ -162,6 +71,11 @@ func (r *OpenGLRenderer) Free() {
|
||||||
|
|
||||||
gl.DeleteProgram(r.program)
|
gl.DeleteProgram(r.program)
|
||||||
r.program = 0
|
r.program = 0
|
||||||
|
|
||||||
|
if r.rectRenderer != nil {
|
||||||
|
r.rectRenderer.Free()
|
||||||
|
r.rectRenderer = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) GetTermSize() (uint, uint) {
|
func (r *OpenGLRenderer) GetTermSize() (uint, uint) {
|
||||||
|
@ -181,26 +95,16 @@ func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight
|
||||||
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) GetRectangleSize(col uint, row uint) (float32, float32) {
|
func (r *OpenGLRenderer) ConvertCoordinates(col uint, row uint) (float32, float32) {
|
||||||
x := float32(float32(col) * r.cellWidth)
|
left := float32(float32(col) * r.cellWidth)
|
||||||
y := float32(float32(row) * r.cellHeight)
|
top := float32(float32(row) * r.cellHeight)
|
||||||
|
|
||||||
return x, y
|
return left, top
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle {
|
|
||||||
x := float32(float32(col) * r.cellWidth)
|
|
||||||
y := float32(float32(row)*r.cellHeight) + r.cellHeight
|
|
||||||
|
|
||||||
return r.newRectangle(x, y, r.colourAttr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) {
|
func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) {
|
||||||
rect := r.getRectangle(col, row)
|
left, top := r.ConvertCoordinates(col, row)
|
||||||
rect.setColour(colour)
|
r.rectRenderer.render(left, top, r.cellWidth, r.cellHeight, colour)
|
||||||
rect.Draw()
|
|
||||||
|
|
||||||
rect.Free()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, colour *config.Colour, force bool) {
|
func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, colour *config.Colour, force bool) {
|
||||||
|
@ -214,13 +118,9 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, colour
|
||||||
}
|
}
|
||||||
|
|
||||||
if bg != r.backgroundColour || force {
|
if bg != r.backgroundColour || force {
|
||||||
rect := r.getRectangle(col, row)
|
left, top := r.ConvertCoordinates(col, row)
|
||||||
rect.setColour(bg)
|
r.rectRenderer.render(left, top, r.cellWidth, r.cellHeight, bg)
|
||||||
rect.Draw()
|
|
||||||
|
|
||||||
rect.Free()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawUnderline draws a line under 'span' characters starting at (col, row)
|
// DrawUnderline draws a line under 'span' characters starting at (col, row)
|
||||||
|
@ -233,12 +133,8 @@ func (r *OpenGLRenderer) DrawUnderline(span int, col uint, row uint, colour [3]f
|
||||||
if thickness < 1 {
|
if thickness < 1 {
|
||||||
thickness = 1
|
thickness = 1
|
||||||
}
|
}
|
||||||
rect := r.newRectangleEx(x, y, r.cellWidth*float32(span), thickness, r.colourAttr)
|
|
||||||
|
|
||||||
rect.setColour(colour)
|
r.rectRenderer.render(x, y, r.cellWidth*float32(span), thickness, colour)
|
||||||
rect.Draw()
|
|
||||||
|
|
||||||
rect.Free()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) DrawCellText(text string, col uint, row uint, alpha float32, colour [3]float32, bold bool) {
|
func (r *OpenGLRenderer) DrawCellText(text string, col uint, row uint, alpha float32, colour [3]float32, bold bool) {
|
||||||
|
|
|
@ -0,0 +1,519 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-gl/gl/all-core/gl"
|
||||||
|
"github.com/go-gl/glfw/v3.2/glfw"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
scrollbarVertexShaderSource = `
|
||||||
|
#version 330 core
|
||||||
|
layout (location = 0) in vec2 position;
|
||||||
|
uniform vec2 resolution;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// convert from window coordinates to GL coordinates
|
||||||
|
vec2 glCoordinates = ((position / resolution) * 2.0 - 1.0) * vec2(1, -1);
|
||||||
|
|
||||||
|
gl_Position = vec4(glCoordinates, 0.0, 1.0);
|
||||||
|
}` + "\x00"
|
||||||
|
|
||||||
|
scrollbarFragmentShaderSource = `
|
||||||
|
#version 330 core
|
||||||
|
uniform vec4 inColor;
|
||||||
|
out vec4 outColor;
|
||||||
|
void main() {
|
||||||
|
outColor = inColor;
|
||||||
|
}` + "\x00"
|
||||||
|
|
||||||
|
NumberOfVertexValues = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
scrollbarColor_Bg = [3]float32{float32(241) / float32(255), float32(241) / float32(255), float32(241) / float32(255)}
|
||||||
|
scrollbarColor_ThumbNormal = [3]float32{float32(193) / float32(255), float32(193) / float32(255), float32(193) / float32(255)}
|
||||||
|
scrollbarColor_ThumbHover = [3]float32{float32(168) / float32(255), float32(168) / float32(255), float32(168) / float32(255)}
|
||||||
|
scrollbarColor_ThumbClicked = [3]float32{float32(120) / float32(255), float32(120) / float32(255), float32(120) / float32(255)}
|
||||||
|
|
||||||
|
scrollbarColor_ButtonNormalBg = [3]float32{float32(241) / float32(255), float32(241) / float32(255), float32(241) / float32(255)}
|
||||||
|
scrollbarColor_ButtonNormalFg = [3]float32{float32(80) / float32(255), float32(80) / float32(255), float32(80) / float32(255)}
|
||||||
|
|
||||||
|
scrollbarColor_ButtonHoverBg = [3]float32{float32(210) / float32(255), float32(210) / float32(255), float32(210) / float32(255)}
|
||||||
|
scrollbarColor_ButtonHoverFg = [3]float32{float32(80) / float32(255), float32(80) / float32(255), float32(80) / float32(255)}
|
||||||
|
|
||||||
|
scrollbarColor_ButtonDisabledBg = [3]float32{float32(241) / float32(255), float32(241) / float32(255), float32(241) / float32(255)}
|
||||||
|
scrollbarColor_ButtonDisabledFg = [3]float32{float32(163) / float32(255), float32(163) / float32(255), float32(163) / float32(255)}
|
||||||
|
|
||||||
|
scrollbarColor_ButtonClickedBg = [3]float32{float32(120) / float32(255), float32(120) / float32(255), float32(120) / float32(255)}
|
||||||
|
scrollbarColor_ButtonClickedFg = [3]float32{float32(255) / float32(255), float32(255) / float32(255), float32(255) / float32(255)}
|
||||||
|
)
|
||||||
|
|
||||||
|
type scrollbarPart int
|
||||||
|
|
||||||
|
const (
|
||||||
|
None scrollbarPart = iota
|
||||||
|
UpperArrow
|
||||||
|
UpperSpace // the space between upper arrow and thumb
|
||||||
|
Thumb
|
||||||
|
BottomSpace // the space between thumb and bottom arrow
|
||||||
|
BottomArrow
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScreenRectangle struct {
|
||||||
|
left, top float32 // upper left corner in pixels relative to the window (in pixels)
|
||||||
|
right, bottom float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *ScreenRectangle) width() float32 {
|
||||||
|
return sr.right - sr.left
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *ScreenRectangle) height() float32 {
|
||||||
|
return sr.bottom - sr.top
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *ScreenRectangle) isInside(x float32, y float32) bool {
|
||||||
|
return x >= sr.left && x < sr.right &&
|
||||||
|
y >= sr.top && y < sr.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
type scrollbar struct {
|
||||||
|
program uint32
|
||||||
|
vbo uint32
|
||||||
|
vao uint32
|
||||||
|
uniformLocationResolution int32
|
||||||
|
uniformLocationInColor int32
|
||||||
|
|
||||||
|
position ScreenRectangle // relative to the window's top left corner, in pixels
|
||||||
|
positionUpperArrow ScreenRectangle // relative to the control's top left corner
|
||||||
|
positionBottomArrow ScreenRectangle
|
||||||
|
positionThumb ScreenRectangle
|
||||||
|
|
||||||
|
scrollPosition int
|
||||||
|
maxScrollPosition int
|
||||||
|
|
||||||
|
thumbIsDragging bool
|
||||||
|
startedDraggingAtPosition int // scrollPosition when the dragging was started
|
||||||
|
startedDraggingAtThumbTop float32 // sb.positionThumb.top when the dragging was started
|
||||||
|
offsetInThumbY float32 // y offset inside the thumb of the dragging point
|
||||||
|
scrollPositionDelta int
|
||||||
|
|
||||||
|
upperArrowIsDown bool
|
||||||
|
bottomArrowIsDown bool
|
||||||
|
|
||||||
|
upperArrowFg []float32
|
||||||
|
upperArrowBg []float32
|
||||||
|
bottomArrowFg []float32
|
||||||
|
bottomArrowBg []float32
|
||||||
|
thumbColor []float32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the vertical scrollbar width in pixels
|
||||||
|
func getDefaultScrollbarWidth() int {
|
||||||
|
return 13
|
||||||
|
}
|
||||||
|
|
||||||
|
func createScrollbarProgram() (uint32, error) {
|
||||||
|
vertexShader, err := compileShader(scrollbarVertexShaderSource, gl.VERTEX_SHADER)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer gl.DeleteShader(vertexShader)
|
||||||
|
|
||||||
|
fragmentShader, err := compileShader(scrollbarFragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer gl.DeleteShader(fragmentShader)
|
||||||
|
|
||||||
|
prog := gl.CreateProgram()
|
||||||
|
gl.AttachShader(prog, vertexShader)
|
||||||
|
gl.AttachShader(prog, fragmentShader)
|
||||||
|
gl.LinkProgram(prog)
|
||||||
|
|
||||||
|
return prog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newScrollbar() (*scrollbar, error) {
|
||||||
|
prog, err := createScrollbarProgram()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vbo uint32
|
||||||
|
var vao uint32
|
||||||
|
|
||||||
|
gl.GenBuffers(1, &vbo)
|
||||||
|
gl.GenVertexArrays(1, &vao)
|
||||||
|
|
||||||
|
gl.BindVertexArray(vao)
|
||||||
|
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||||
|
gl.BufferData(gl.ARRAY_BUFFER, NumberOfVertexValues*4, nil, gl.DYNAMIC_DRAW) // only reserve the space
|
||||||
|
|
||||||
|
gl.VertexAttribPointer(0, 2, gl.FLOAT, false, 2*4, nil)
|
||||||
|
gl.EnableVertexAttribArray(0)
|
||||||
|
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||||
|
gl.BindVertexArray(0)
|
||||||
|
|
||||||
|
result := &scrollbar{
|
||||||
|
program: prog,
|
||||||
|
vbo: vbo,
|
||||||
|
vao: vao,
|
||||||
|
uniformLocationResolution: gl.GetUniformLocation(prog, gl.Str("resolution\x00")),
|
||||||
|
uniformLocationInColor: gl.GetUniformLocation(prog, gl.Str("inColor\x00")),
|
||||||
|
|
||||||
|
position: ScreenRectangle{
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollPosition: 0,
|
||||||
|
maxScrollPosition: 0,
|
||||||
|
|
||||||
|
thumbIsDragging: false,
|
||||||
|
upperArrowIsDown: false,
|
||||||
|
bottomArrowIsDown: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
result.recalcElementPositions()
|
||||||
|
result.resetElementColors(-1, -1) // (-1, -1) ensures that no part is hovered by the mouse
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) Free() {
|
||||||
|
if sb.program != 0 {
|
||||||
|
gl.DeleteProgram(sb.program)
|
||||||
|
sb.program = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.vbo != 0 {
|
||||||
|
gl.DeleteBuffers(1, &sb.vbo)
|
||||||
|
sb.vbo = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.vao != 0 {
|
||||||
|
gl.DeleteBuffers(1, &sb.vao)
|
||||||
|
sb.vao = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalc positions of the scrollbar elements according to current
|
||||||
|
func (sb *scrollbar) recalcElementPositions() {
|
||||||
|
arrowHeight := sb.position.width()
|
||||||
|
|
||||||
|
sb.positionUpperArrow = ScreenRectangle{
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: sb.position.width(),
|
||||||
|
bottom: arrowHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.positionBottomArrow = ScreenRectangle{
|
||||||
|
left: sb.positionUpperArrow.left,
|
||||||
|
top: sb.position.height() - arrowHeight,
|
||||||
|
right: sb.positionUpperArrow.right,
|
||||||
|
bottom: sb.position.height(),
|
||||||
|
}
|
||||||
|
thumbHeight := sb.position.width()
|
||||||
|
thumbTop := arrowHeight
|
||||||
|
if sb.maxScrollPosition != 0 {
|
||||||
|
thumbTop += (float32(sb.scrollPosition) * (sb.position.height() - thumbHeight - arrowHeight*2)) / float32(sb.maxScrollPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.positionThumb = ScreenRectangle{
|
||||||
|
left: 2,
|
||||||
|
top: thumbTop,
|
||||||
|
right: sb.position.width() - 2,
|
||||||
|
bottom: thumbTop + thumbHeight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) resize(gui *GUI) {
|
||||||
|
sb.position.left = float32(gui.width) - float32(getDefaultScrollbarWidth())*gui.dpiScale
|
||||||
|
sb.position.top = float32(0.0)
|
||||||
|
sb.position.right = float32(gui.width)
|
||||||
|
sb.position.bottom = float32(gui.height - 1)
|
||||||
|
|
||||||
|
sb.recalcElementPositions()
|
||||||
|
gui.terminal.NotifyDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) render(gui *GUI) {
|
||||||
|
var savedProgram int32
|
||||||
|
gl.GetIntegerv(gl.CURRENT_PROGRAM, &savedProgram)
|
||||||
|
defer gl.UseProgram(uint32(savedProgram))
|
||||||
|
|
||||||
|
gl.UseProgram(sb.program)
|
||||||
|
gl.Uniform2f(sb.uniformLocationResolution, float32(gui.width), float32(gui.height))
|
||||||
|
gl.BindVertexArray(sb.vao)
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, sb.vbo)
|
||||||
|
defer func() {
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||||
|
gl.BindVertexArray(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
gl.Uniform4f(sb.uniformLocationInColor, scrollbarColor_Bg[0], scrollbarColor_Bg[1], scrollbarColor_Bg[2], 1.0)
|
||||||
|
borderVertices := [...]float32{
|
||||||
|
sb.position.left, sb.position.top,
|
||||||
|
sb.position.right, sb.position.top,
|
||||||
|
sb.position.right, sb.position.bottom,
|
||||||
|
|
||||||
|
sb.position.right, sb.position.bottom,
|
||||||
|
sb.position.left, sb.position.bottom,
|
||||||
|
sb.position.left, sb.position.top,
|
||||||
|
}
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(borderVertices)*4, gl.Ptr(&borderVertices[0]))
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(borderVertices)/2))
|
||||||
|
|
||||||
|
// Draw upper arrow
|
||||||
|
// Upper arrow background
|
||||||
|
gl.Uniform4f(sb.uniformLocationInColor, sb.upperArrowBg[0], sb.upperArrowBg[1], sb.upperArrowBg[2], 1.0)
|
||||||
|
upperArrowBgVertices := [...]float32{
|
||||||
|
sb.position.left + sb.positionUpperArrow.left, sb.position.top + sb.positionUpperArrow.top,
|
||||||
|
sb.position.left + sb.positionUpperArrow.right, sb.position.top + sb.positionUpperArrow.top,
|
||||||
|
sb.position.left + sb.positionUpperArrow.right, sb.position.top + sb.positionUpperArrow.bottom,
|
||||||
|
|
||||||
|
sb.position.left + sb.positionUpperArrow.right, sb.position.top + sb.positionUpperArrow.bottom,
|
||||||
|
sb.position.left + sb.positionUpperArrow.left, sb.position.top + sb.positionUpperArrow.bottom,
|
||||||
|
sb.position.left + sb.positionUpperArrow.left, sb.position.top + sb.positionUpperArrow.top,
|
||||||
|
}
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(upperArrowBgVertices)*4, gl.Ptr(&upperArrowBgVertices[0]))
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(upperArrowBgVertices)/2))
|
||||||
|
|
||||||
|
// Upper arrow foreground
|
||||||
|
gl.Uniform4f(sb.uniformLocationInColor, sb.upperArrowFg[0], sb.upperArrowFg[1], sb.upperArrowFg[2], 1.0)
|
||||||
|
upperArrowFgVertices := [...]float32{
|
||||||
|
sb.position.left + sb.positionUpperArrow.left + sb.positionUpperArrow.width()/2.0, sb.position.top + sb.positionUpperArrow.top + sb.positionUpperArrow.height()/3.0,
|
||||||
|
sb.position.left + sb.positionUpperArrow.left + sb.positionUpperArrow.width()*2.0/3.0, sb.position.top + sb.positionUpperArrow.top + sb.positionUpperArrow.height()/2.0,
|
||||||
|
sb.position.left + sb.positionUpperArrow.left + sb.positionUpperArrow.width()/3.0, sb.position.top + sb.positionUpperArrow.top + sb.positionUpperArrow.height()/2.0,
|
||||||
|
}
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(upperArrowFgVertices)*4, gl.Ptr(&upperArrowFgVertices[0]))
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(upperArrowFgVertices)/2))
|
||||||
|
|
||||||
|
// Draw bottom arrow
|
||||||
|
// Bottom arrow background
|
||||||
|
gl.Uniform4f(sb.uniformLocationInColor, sb.bottomArrowBg[0], sb.bottomArrowBg[1], sb.bottomArrowBg[2], 1.0)
|
||||||
|
bottomArrowBgVertices := [...]float32{
|
||||||
|
sb.position.left + sb.positionBottomArrow.left, sb.position.top + sb.positionBottomArrow.top,
|
||||||
|
sb.position.left + sb.positionBottomArrow.right, sb.position.top + sb.positionBottomArrow.top,
|
||||||
|
sb.position.left + sb.positionBottomArrow.right, sb.position.top + sb.positionBottomArrow.bottom,
|
||||||
|
|
||||||
|
sb.position.left + sb.positionBottomArrow.right, sb.position.top + sb.positionBottomArrow.bottom,
|
||||||
|
sb.position.left + sb.positionBottomArrow.left, sb.position.top + sb.positionBottomArrow.bottom,
|
||||||
|
sb.position.left + sb.positionBottomArrow.left, sb.position.top + sb.positionBottomArrow.top,
|
||||||
|
}
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(bottomArrowBgVertices)*4, gl.Ptr(&bottomArrowBgVertices[0]))
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(bottomArrowBgVertices)/2))
|
||||||
|
|
||||||
|
// Bottom arrow foreground
|
||||||
|
gl.Uniform4f(sb.uniformLocationInColor, sb.bottomArrowFg[0], sb.bottomArrowFg[1], sb.bottomArrowFg[2], 1.0)
|
||||||
|
bottomArrowFgVertices := [...]float32{
|
||||||
|
sb.position.left + sb.positionBottomArrow.left + sb.positionBottomArrow.width()/3.0, sb.position.top + sb.positionBottomArrow.top + sb.positionBottomArrow.height()/2.0,
|
||||||
|
sb.position.left + sb.positionBottomArrow.left + sb.positionBottomArrow.width()*2.0/3.0, sb.position.top + sb.positionBottomArrow.top + sb.positionBottomArrow.height()/2.0,
|
||||||
|
sb.position.left + sb.positionBottomArrow.left + sb.positionBottomArrow.width()/2.0, sb.position.top + sb.positionBottomArrow.top + sb.positionBottomArrow.height()*2.0/3.0,
|
||||||
|
}
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(bottomArrowFgVertices)*4, gl.Ptr(&bottomArrowFgVertices[0]))
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(bottomArrowFgVertices)/2))
|
||||||
|
|
||||||
|
// Draw thumb
|
||||||
|
gl.Uniform4f(sb.uniformLocationInColor, sb.thumbColor[0], sb.thumbColor[1], sb.thumbColor[2], 1.0)
|
||||||
|
thumbVertices := [...]float32{
|
||||||
|
sb.position.left + sb.positionThumb.left, sb.position.top + sb.positionThumb.top,
|
||||||
|
sb.position.left + sb.positionThumb.right, sb.position.top + sb.positionThumb.top,
|
||||||
|
sb.position.left + sb.positionThumb.right, sb.position.top + sb.positionThumb.bottom,
|
||||||
|
|
||||||
|
sb.position.left + sb.positionThumb.right, sb.position.top + sb.positionThumb.bottom,
|
||||||
|
sb.position.left + sb.positionThumb.left, sb.position.top + sb.positionThumb.bottom,
|
||||||
|
sb.position.left + sb.positionThumb.left, sb.position.top + sb.positionThumb.top,
|
||||||
|
}
|
||||||
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(thumbVertices)*4, gl.Ptr(&thumbVertices[0]))
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(thumbVertices)/2))
|
||||||
|
|
||||||
|
gui.terminal.NotifyDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) setPosition(max int, position int) {
|
||||||
|
if max <= 0 {
|
||||||
|
max = position
|
||||||
|
}
|
||||||
|
|
||||||
|
if position > max {
|
||||||
|
position = max
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.maxScrollPosition = max
|
||||||
|
sb.scrollPosition = position
|
||||||
|
|
||||||
|
sb.recalcElementPositions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) mouseHitTest(px float64, py float64) scrollbarPart {
|
||||||
|
// convert to local coordinates
|
||||||
|
mouseX := float32(px - float64(sb.position.left))
|
||||||
|
mouseY := float32(py - float64(sb.position.top))
|
||||||
|
|
||||||
|
result := None
|
||||||
|
|
||||||
|
if sb.positionUpperArrow.isInside(mouseX, mouseY) {
|
||||||
|
result = UpperArrow
|
||||||
|
} else if sb.positionBottomArrow.isInside(mouseX, mouseY) {
|
||||||
|
result = BottomArrow
|
||||||
|
} else if sb.positionThumb.isInside(mouseX, mouseY) {
|
||||||
|
result = Thumb
|
||||||
|
} else {
|
||||||
|
// construct UpperSpace
|
||||||
|
pos := ScreenRectangle{
|
||||||
|
left: sb.positionThumb.left,
|
||||||
|
top: sb.positionUpperArrow.bottom,
|
||||||
|
right: sb.positionThumb.right,
|
||||||
|
bottom: sb.positionThumb.top,
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos.isInside(mouseX, mouseY) {
|
||||||
|
result = UpperSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
// now update it to be BottomSpace
|
||||||
|
pos.top = sb.positionThumb.bottom
|
||||||
|
pos.bottom = sb.positionBottomArrow.top
|
||||||
|
if pos.isInside(mouseX, mouseY) {
|
||||||
|
result = BottomSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) isMouseInside(px float64, py float64) bool {
|
||||||
|
return sb.position.isInside(float32(px), float32(py))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) mouseButtonCallback(g *GUI, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey, mouseX float64, mouseY float64) {
|
||||||
|
if button == glfw.MouseButtonLeft {
|
||||||
|
if action == glfw.Press {
|
||||||
|
switch sb.mouseHitTest(mouseX, mouseY) {
|
||||||
|
case UpperArrow:
|
||||||
|
sb.upperArrowIsDown = true
|
||||||
|
g.terminal.ScreenScrollUp(1)
|
||||||
|
|
||||||
|
case UpperSpace:
|
||||||
|
g.terminal.ScrollPageUp()
|
||||||
|
|
||||||
|
case Thumb:
|
||||||
|
sb.thumbIsDragging = true
|
||||||
|
sb.startedDraggingAtPosition = sb.scrollPosition
|
||||||
|
sb.startedDraggingAtThumbTop = sb.positionThumb.top
|
||||||
|
sb.offsetInThumbY = float32(mouseY) - sb.position.top - sb.positionThumb.top
|
||||||
|
sb.scrollPositionDelta = 0
|
||||||
|
|
||||||
|
case BottomSpace:
|
||||||
|
g.terminal.ScrollPageDown()
|
||||||
|
|
||||||
|
case BottomArrow:
|
||||||
|
sb.bottomArrowIsDown = true
|
||||||
|
g.terminal.ScreenScrollDown(1)
|
||||||
|
}
|
||||||
|
} else if action == glfw.Release {
|
||||||
|
if sb.thumbIsDragging {
|
||||||
|
sb.thumbIsDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.upperArrowIsDown {
|
||||||
|
sb.upperArrowIsDown = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.bottomArrowIsDown {
|
||||||
|
sb.bottomArrowIsDown = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.terminal.NotifyDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.resetElementColors(mouseX, mouseY)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) mouseMoveCallback(g *GUI, px float64, py float64) {
|
||||||
|
sb.resetElementColors(px, py)
|
||||||
|
|
||||||
|
if sb.thumbIsDragging {
|
||||||
|
py -= float64(sb.position.top)
|
||||||
|
|
||||||
|
minThumbTop := sb.positionUpperArrow.bottom
|
||||||
|
maxThumbTop := sb.positionBottomArrow.top - sb.positionThumb.height()
|
||||||
|
|
||||||
|
newThumbTop := float32(py) - sb.offsetInThumbY
|
||||||
|
|
||||||
|
newPositionDelta := int((float32(sb.maxScrollPosition) * (newThumbTop - minThumbTop - sb.startedDraggingAtThumbTop)) / (maxThumbTop - minThumbTop))
|
||||||
|
|
||||||
|
if newPositionDelta > sb.scrollPositionDelta {
|
||||||
|
scrollLines := newPositionDelta - sb.scrollPositionDelta
|
||||||
|
g.logger.Debugf("old position: %d, new position delta: %d, scroll down %d lines", sb.scrollPosition, newPositionDelta, scrollLines)
|
||||||
|
g.terminal.ScreenScrollDown(uint16(scrollLines))
|
||||||
|
sb.scrollPositionDelta = newPositionDelta
|
||||||
|
} else if newPositionDelta < sb.scrollPositionDelta {
|
||||||
|
scrollLines := sb.scrollPositionDelta - newPositionDelta
|
||||||
|
g.logger.Debugf("old position: %d, new position delta: %d, scroll up %d lines", sb.scrollPosition, newPositionDelta, scrollLines)
|
||||||
|
g.terminal.ScreenScrollUp(uint16(scrollLines))
|
||||||
|
sb.scrollPositionDelta = newPositionDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.recalcElementPositions()
|
||||||
|
g.logger.Debugf("new thumbTop: %f, fact thumbTop: %f, position: %d", newThumbTop, sb.positionThumb.top, sb.scrollPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.terminal.NotifyDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) resetElementColors(mouseX float64, mouseY float64) {
|
||||||
|
part := sb.mouseHitTest(mouseX, mouseY)
|
||||||
|
|
||||||
|
if sb.scrollPosition == 0 {
|
||||||
|
sb.upperArrowBg = scrollbarColor_ButtonDisabledBg[:]
|
||||||
|
sb.upperArrowFg = scrollbarColor_ButtonDisabledFg[:]
|
||||||
|
} else if sb.upperArrowIsDown {
|
||||||
|
sb.upperArrowFg = scrollbarColor_ButtonClickedFg[:]
|
||||||
|
sb.upperArrowBg = scrollbarColor_ButtonClickedBg[:]
|
||||||
|
} else if part == UpperArrow {
|
||||||
|
sb.upperArrowFg = scrollbarColor_ButtonHoverFg[:]
|
||||||
|
sb.upperArrowBg = scrollbarColor_ButtonHoverBg[:]
|
||||||
|
} else {
|
||||||
|
sb.upperArrowFg = scrollbarColor_ButtonNormalFg[:]
|
||||||
|
sb.upperArrowBg = scrollbarColor_ButtonNormalBg[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.scrollPosition == sb.maxScrollPosition {
|
||||||
|
sb.bottomArrowBg = scrollbarColor_ButtonDisabledBg[:]
|
||||||
|
sb.bottomArrowFg = scrollbarColor_ButtonDisabledFg[:]
|
||||||
|
} else if sb.bottomArrowIsDown {
|
||||||
|
sb.bottomArrowFg = scrollbarColor_ButtonClickedFg[:]
|
||||||
|
sb.bottomArrowBg = scrollbarColor_ButtonClickedBg[:]
|
||||||
|
} else if part == BottomArrow {
|
||||||
|
sb.bottomArrowFg = scrollbarColor_ButtonHoverFg[:]
|
||||||
|
sb.bottomArrowBg = scrollbarColor_ButtonHoverBg[:]
|
||||||
|
} else {
|
||||||
|
sb.bottomArrowFg = scrollbarColor_ButtonNormalFg[:]
|
||||||
|
sb.bottomArrowBg = scrollbarColor_ButtonNormalBg[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.thumbIsDragging {
|
||||||
|
sb.thumbColor = scrollbarColor_ThumbClicked[:]
|
||||||
|
} else if part == Thumb {
|
||||||
|
sb.thumbColor = scrollbarColor_ThumbHover[:]
|
||||||
|
} else {
|
||||||
|
sb.thumbColor = scrollbarColor_ThumbNormal[:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *scrollbar) cursorEnterCallback(g *GUI, entered bool) {
|
||||||
|
if !entered {
|
||||||
|
sb.resetElementColors(-1, -1) // (-1, -1) ensures that no part is hovered by the mouse
|
||||||
|
g.terminal.NotifyDirty()
|
||||||
|
}
|
||||||
|
}
|
1
main.go
1
main.go
|
@ -72,6 +72,7 @@ func initialize(unitTestfunc callback, configOverride *config.Config) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("Cannot start: %s", err)
|
logger.Fatalf("Cannot start: %s", err)
|
||||||
}
|
}
|
||||||
|
defer g.Free()
|
||||||
|
|
||||||
terminal.WindowManipulation = g
|
terminal.WindowManipulation = g
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,9 @@ func TestExit(t *testing.T) {
|
||||||
func testConfig() *config.Config {
|
func testConfig() *config.Config {
|
||||||
c := config.DefaultConfig()
|
c := config.DefaultConfig()
|
||||||
|
|
||||||
|
// Force the scrollbar not showing when running unit tests
|
||||||
|
c.ShowVerticalScrollbar = false
|
||||||
|
|
||||||
// Use a vanilla shell on POSIX to help ensure consistency.
|
// Use a vanilla shell on POSIX to help ensure consistency.
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
c.Shell = "/bin/sh"
|
c.Shell = "/bin/sh"
|
||||||
|
|
Loading…
Reference in New Issue