Add basic dropdown functionality

This commit is contained in:
EnumDev 2025-06-04 16:00:37 +03:00
parent b52729646c
commit a2876c2086
6 changed files with 291 additions and 45 deletions

View File

@ -68,6 +68,7 @@ func CreateFileBuffer(filename string) (*Buffer, error) {
}
Buffers[buffer.Id] = &buffer
LastBufferId++
return &buffer, nil
}
@ -82,6 +83,7 @@ func CreateBuffer(bufferName string) *Buffer {
}
Buffers[buffer.Id] = &buffer
LastBufferId++
return &buffer
}

72
src/dropdown.go Normal file
View File

@ -0,0 +1,72 @@
package main
import (
"github.com/gdamore/tcell"
)
type Dropdown struct {
Active bool
Selected int
Options []string
PosX, PosY int
Width int
Action func(int)
}
var dropdowns = make([]*Dropdown, 0)
func CreateDropdownMenu(options []string, posX, posY, dropdownWidth int, action func(int)) *Dropdown {
if len(options) == 0 {
return nil
}
width := 0
if dropdownWidth <= 0 {
for _, option := range options {
if len(option) > width {
width = len(option)
}
}
}
d := &Dropdown{
Active: false,
Selected: 0,
Options: options,
PosX: posX,
PosY: posY,
Width: width,
Action: action,
}
dropdowns = append(dropdowns, d)
return d
}
func GetActiveDropdown() *Dropdown {
for _, dropdown := range dropdowns {
if dropdown.Active {
return dropdown
}
}
return nil
}
func drawDropdowns(window *Window) {
dropdownStyle := tcell.StyleDefault.Foreground(tcell.ColorBlack).Background(tcell.ColorWhite)
for _, d := range dropdowns {
drawBox(window.screen, d.PosX, d.PosY, d.PosX+d.Width+1, d.PosY+len(d.Options)+1, dropdownStyle)
line := d.PosY
for i, option := range d.Options {
if d.Selected == i {
drawText(window.screen, d.PosX+1, d.PosY+line, d.PosX+d.Width+1, d.PosY+line, dropdownStyle.Background(tcell.Color250), option)
} else {
drawText(window.screen, d.PosX+1, d.PosY+line, d.PosX+d.Width+1, d.PosY+line, dropdownStyle, option)
}
line++
}
}
}

View File

@ -20,7 +20,7 @@ func main() {
PrintMessage(window, "Could not open file: "+file)
continue
}
Buffers[b.Id] = b
if initialBuffer == nil {
initialBuffer = b
}
@ -28,7 +28,6 @@ func main() {
}
if initialBuffer != nil {
delete(Buffers, window.textArea.CurrentBuffer.Id)
window.textArea.CurrentBuffer = initialBuffer
}

View File

@ -1,11 +1,18 @@
package main
import (
"fmt"
"github.com/gdamore/tcell"
"maps"
"slices"
"strconv"
"strings"
)
type TopMenuButton struct {
Name string
Name string
Key rune
Action func(w *Window)
}
var TopMenuButtons = make([]TopMenuButton, 0)
@ -14,12 +21,80 @@ func initTopMenu() {
// Buttons
fileButton := TopMenuButton{
Name: "File",
Key: 'f',
Action: func(window *Window) {
dropdowns = make([]*Dropdown, 0)
d := CreateDropdownMenu([]string{"New", "Save", "Open", "Close", "Quit"}, 0, 1, 0, func(i int) {
switch i {
case 0:
number := 1
for _, buffer := range Buffers {
if strings.HasPrefix(buffer.Name, "New File ") {
number++
}
}
buffer := CreateBuffer(fmt.Sprintf("New File %d", number))
window.textArea.CurrentBuffer = buffer
window.SetCursorPos(0)
case 1:
case 2:
case 3:
delete(Buffers, window.textArea.CurrentBuffer.Id)
buffersSlice := slices.Collect(maps.Values(Buffers))
if len(buffersSlice) == 0 {
window.Close()
return
}
window.textArea.CurrentBuffer = buffersSlice[0]
window.SetCursorPos(0)
case 4:
window.Close()
}
dropdowns = make([]*Dropdown, 0)
window.textArea.Typing = true
})
d.Active = true
window.textArea.Typing = false
},
}
EditButton := TopMenuButton{
Name: "Edit",
Key: 'e',
}
Buffers := TopMenuButton{
Name: "Buffers",
Key: 'b',
Action: func(window *Window) {
dropdowns = make([]*Dropdown, 0)
buffersSlice := make([]string, 0)
for _, buffer := range Buffers {
if window.textArea.CurrentBuffer == buffer {
buffersSlice = append(buffersSlice, fmt.Sprintf("[%d] * %s", buffer.Id, buffer.Name))
} else {
buffersSlice = append(buffersSlice, fmt.Sprintf("[%d] %s", buffer.Id, buffer.Name))
}
}
slices.Sort(buffersSlice)
d := CreateDropdownMenu(buffersSlice, 0, 1, 0, func(i int) {
start := strings.Index(buffersSlice[i], "[")
end := strings.Index(buffersSlice[i], "]")
id, err := strconv.Atoi(buffersSlice[i][start+1 : end])
if err != nil {
PrintMessage(window, fmt.Sprintf("Cannot convert buffer id '%s' to int", buffersSlice[i][start:end]))
return
}
window.textArea.CurrentBuffer = Buffers[id]
window.SetCursorPos(0)
dropdowns = make([]*Dropdown, 0)
window.textArea.Typing = true
})
d.Active = true
window.textArea.Typing = false
},
}
// Append buttons

View File

@ -17,3 +17,39 @@ func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string
}
}
}
func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style) {
if y2 < y1 {
y1, y2 = y2, y1
}
if x2 < x1 {
x1, x2 = x2, x1
}
// Fill background
for row := y1; row <= y2; row++ {
for col := x1; col <= x2; col++ {
s.SetContent(col, row, ' ', nil, style)
}
}
// Draw borders
for col := x1; col <= x2; col++ {
s.SetContent(col, y1, tcell.RuneHLine, nil, style)
s.SetContent(col, y2, tcell.RuneHLine, nil, style)
}
for row := y1 + 1; row < y2; row++ {
s.SetContent(x1, row, tcell.RuneVLine, nil, style)
s.SetContent(x2, row, tcell.RuneVLine, nil, style)
}
// Only draw corners if necessary
if y1 != y2 && x1 != x2 {
s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
}
drawText(s, x1+1, y1+1, x2-1, y2-1, style, " ")
}

View File

@ -3,6 +3,8 @@ package main
import (
"github.com/gdamore/tcell"
"log"
"maps"
"slices"
)
type Window struct {
@ -16,6 +18,7 @@ type Window struct {
type TextArea struct {
CursorPos int
Typing bool
CurrentBuffer *Buffer
}
@ -26,6 +29,7 @@ func CreateWindow() (*Window, error) {
textArea: TextArea{
CursorPos: 0,
Typing: true,
CurrentBuffer: nil,
},
@ -33,7 +37,7 @@ func CreateWindow() (*Window, error) {
}
// Create empty buffer if nil
window.textArea.CurrentBuffer = CreateBuffer("New File")
window.textArea.CurrentBuffer = CreateBuffer("New File 1")
// Create tcell screen
screen, err := tcell.NewScreen()
@ -109,8 +113,15 @@ func (window *Window) Draw() {
// Draw message bar
drawMessageBar(window)
// Draw dropdowns
drawDropdowns(window)
// Draw cursor
window.screen.ShowCursor(window.GetAbsoluteCursorPos())
if window.textArea.Typing {
window.screen.ShowCursor(window.GetAbsoluteCursorPos())
} else {
window.screen.HideCursor()
}
// Update screen
window.screen.Show()
@ -123,46 +134,93 @@ func (window *Window) Draw() {
case *tcell.EventResize:
window.screen.Sync()
case *tcell.EventKey:
// Navigation Keys
if ev.Key() == tcell.KeyRight {
window.input(ev)
}
}
func (window *Window) input(ev *tcell.EventKey) {
if ev.Key() == tcell.KeyRight { // Navigation Keys
if window.textArea.Typing {
window.SetCursorPos(window.textArea.CursorPos + 1)
} else if ev.Key() == tcell.KeyLeft {
}
} else if ev.Key() == tcell.KeyLeft {
if window.textArea.Typing {
window.SetCursorPos(window.textArea.CursorPos - 1)
} else if ev.Key() == tcell.KeyUp {
}
} else if ev.Key() == tcell.KeyUp {
if window.textArea.Typing {
x, y := window.GetCursorPos2D()
window.SetCursorPos2D(x, y-1)
} else if ev.Key() == tcell.KeyDown {
} else if GetActiveDropdown() != nil {
dropdown := GetActiveDropdown()
dropdown.Selected--
if dropdown.Selected < 0 {
dropdown.Selected = 0
}
}
} else if ev.Key() == tcell.KeyDown {
if window.textArea.Typing {
x, y := window.GetCursorPos2D()
window.SetCursorPos2D(x, y+1)
} else if GetActiveDropdown() != nil {
dropdown := GetActiveDropdown()
dropdown.Selected++
if dropdown.Selected >= len(dropdown.Options) {
dropdown.Selected = len(dropdown.Options) - 1
}
}
// Exit key
if ev.Key() == tcell.KeyCtrlC {
} else if ev.Key() == tcell.KeyEscape {
dropdowns = make([]*Dropdown, 0)
window.textArea.Typing = true
} else if ev.Key() == tcell.KeyCtrlC { // Close buffer key
delete(Buffers, window.textArea.CurrentBuffer.Id)
buffersSlice := slices.Collect(maps.Values(Buffers))
if len(buffersSlice) == 0 {
window.Close()
return
}
window.textArea.CurrentBuffer = buffersSlice[0]
window.SetCursorPos(0)
dropdowns = make([]*Dropdown, 0)
window.textArea.Typing = true
} else if ev.Key() == tcell.KeyCtrlQ { // Exit key
window.Close()
} else if ev.Modifiers()&tcell.ModAlt != 0 { // Menu Bar
for _, button := range TopMenuButtons {
if ev.Rune() == button.Key {
button.Action(window)
break
}
}
} else if ev.Key() == tcell.KeyBackspace2 { // Typing
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
if index != 0 {
str = str[:index-1] + str[index:]
window.textArea.CursorPos--
window.textArea.CurrentBuffer.Contents = str
}
} else if ev.Key() == tcell.KeyTab {
if GetActiveDropdown() != nil {
return
}
// Typing
if ev.Key() == tcell.KeyBackspace2 {
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
if index != 0 {
str = str[:index-1] + str[index:]
window.textArea.CursorPos--
window.textArea.CurrentBuffer.Contents = str
}
} else if ev.Key() == tcell.KeyTab {
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
if index == len(str) {
str += "\t"
} else {
str = str[:index] + "\t" + str[index:]
}
window.textArea.CursorPos++
window.textArea.CurrentBuffer.Contents = str
} else if ev.Key() == tcell.KeyEnter {
if index == len(str) {
str += "\t"
} else {
str = str[:index] + "\t" + str[index:]
}
window.textArea.CursorPos++
window.textArea.CurrentBuffer.Contents = str
} else if ev.Key() == tcell.KeyEnter {
if GetActiveDropdown() != nil {
d := GetActiveDropdown()
d.Action(d.Selected)
} else {
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
@ -173,18 +231,22 @@ func (window *Window) Draw() {
}
window.textArea.CursorPos++
window.textArea.CurrentBuffer.Contents = str
} else if ev.Key() == tcell.KeyRune {
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
if index == len(str) {
str += string(ev.Rune())
} else {
str = str[:index] + string(ev.Rune()) + str[index:]
}
window.textArea.CursorPos++
window.textArea.CurrentBuffer.Contents = str
}
} else if ev.Key() == tcell.KeyRune {
if GetActiveDropdown() != nil {
return
}
str := window.textArea.CurrentBuffer.Contents
index := window.textArea.CursorPos
if index == len(str) {
str += string(ev.Rune())
} else {
str = str[:index] + string(ev.Rune()) + str[index:]
}
window.textArea.CursorPos++
window.textArea.CurrentBuffer.Contents = str
}
}