Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better modal #79

Merged
merged 4 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/default_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ func getHelpMenuData() []helpMenuModalData {
description: "Quit",
hotkeyWorkType: globalType,
},
{
hotkey: hotkeys.OpenHelpMenu,
description: "Open help menu(hotkeylist)",
hotkeyWorkType: globalType,
},
{
subTitle: "Panel navigation",
},
Expand Down
61 changes: 38 additions & 23 deletions src/components/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ func InitialModel(dir string) model {
},
helpMenu: helpMenuModal{
renderIndex: 0,
cursor: 1,
data: getHelpMenuData(),
open: false,
cursor: 1,
data: getHelpMenuData(),
open: false,
},
toggleDotFile: toggleDotFileBool,
}
Expand Down Expand Up @@ -127,8 +127,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.fullWidth = msg.Width
m.fileModel.maxFilePanel = (msg.Width - 20) / 24

m.helpMenu.height = m.fullHeight -2
m.helpMenu.width = m.fullWidth -2
m.helpMenu.height = m.fullHeight - 2
m.helpMenu.width = m.fullWidth - 2

if m.fullHeight > 32 {
m.helpMenu.height = 30
Expand Down Expand Up @@ -160,7 +160,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

m, cmd = mainKey(msg.String(), m, cmd)

if m.editorMode {
m.editorMode = false
return m, cmd
Expand Down Expand Up @@ -216,30 +216,45 @@ func (m model) View() string {
// check is the terminal size enough
if m.fullHeight < minimumHeight || m.fullWidth < minimumWidth {
return terminalSizeWarnRender(m)
} else if m.typingModal.open {
return typineModalRender(m)
} else if m.warnModal.open {
return warnModalRender(m)
} else if m.helpMenu.open {
return helpMenuRender(m)
} else {
sidebar := sidebarRender(m)

filePanel := filePanelRender(m)
}
sidebar := sidebarRender(m)

filePanel := filePanelRender(m)

mainPanel := lipgloss.JoinHorizontal(0, sidebar, filePanel)

mainPanel := lipgloss.JoinHorizontal(0, sidebar, filePanel)
processBar := processBarRender(m)

processBar := processBarRender(m)
metaData := metadataRender(m)

metaData := metadataRender(m)
clipboardBar := clipboardRender(m)

clipboardBar := clipboardRender(m)
footer := lipgloss.JoinHorizontal(0, processBar, metaData, clipboardBar)

footer := lipgloss.JoinHorizontal(0, processBar, metaData, clipboardBar)
// final render
finalRender := lipgloss.JoinVertical(0, mainPanel, footer)

// final render
finalRender := lipgloss.JoinVertical(0, mainPanel, footer)
if m.helpMenu.open {
helpMenu := helpMenuRender(m)
overlayX := m.fullWidth / 2 - m.helpMenu.width / 2
overlayY := m.fullHeight / 2 - m.helpMenu.height / 2

return PlaceOverlay(overlayX, overlayY, helpMenu, finalRender)
}

if m.typingModal.open {
typingModal := typineModalRender(m)
overlayX := m.fullWidth / 2 - modalWidth / 2
overlayY := m.fullHeight / 2 - modalHeight / 2
return PlaceOverlay(overlayX, overlayY, typingModal, finalRender)
}

return lipgloss.JoinVertical(lipgloss.Top, finalRender)
if m.warnModal.open {
warnModal := warnModalRender(m)
overlayX := m.fullWidth / 2 - modalWidth / 2
overlayY := m.fullHeight / 2 - modalHeight / 2
return PlaceOverlay(overlayX, overlayY, warnModal, finalRender)
}
return finalRender
}
9 changes: 5 additions & 4 deletions src/components/model_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,23 +335,24 @@ func typineModalRender(m model) string {
confirm := modalConfirm.Render(" (" + hotkeys.Confirm[0] + ") New File ")
cancel := modalCancel.Render(" (" + hotkeys.Cancel[0] + ") Cancel ")
tip := confirm + lipgloss.NewStyle().Background(modalBGColor).Render(" ") + cancel
return fullScreenStyle(m.fullHeight, m.fullWidth).Render(modalBorderStyle(modalHeight, modalWidth).Render(fileLocation + "\n" + m.typingModal.textInput.View() + "\n\n" + tip))
return modalBorderStyle(modalHeight, modalWidth).Render(fileLocation + "\n" + m.typingModal.textInput.View() + "\n\n" + tip)
} else {
fileLocation := filePanelTopDirectoryIconStyle.Render("  ") + filePanelTopPathStyle.Render(truncateTextBeginning(m.typingModal.location+"/"+m.typingModal.textInput.Value(), modalWidth-4)) + "\n"
confirm := modalConfirm.Render(" (" + hotkeys.Confirm[0] + ") New Folder ")
cancel := modalCancel.Render(" (" + hotkeys.Cancel[0] + ") Cancel ")
tip := confirm + lipgloss.NewStyle().Background(modalBGColor).Render(" ") + cancel
return fullScreenStyle(m.fullHeight, m.fullWidth).Render(modalBorderStyle(modalHeight, modalWidth).Render(fileLocation + "\n" + m.typingModal.textInput.View() + "\n\n" + tip))
return modalBorderStyle(modalHeight, modalWidth).Render(fileLocation + "\n" + m.typingModal.textInput.View() + "\n\n" + tip)
}
}


func warnModalRender(m model) string {
title := m.warnModal.title
content := m.warnModal.content
confirm := modalCancel.Render(" (" + hotkeys.Confirm[0] + ") Confirm ")
cancel := modalCancel.Render(" (" + hotkeys.Cancel[0] + ") Cancel ")
tip := confirm + lipgloss.NewStyle().Background(modalBGColor).Render(" ") + cancel
return fullScreenStyle(m.fullHeight, m.fullWidth).Render(modalBorderStyle(modalHeight, modalWidth).Render(title + "\n\n" + content + "\n\n" + tip))
return modalBorderStyle(modalHeight, modalWidth).Render(title + "\n\n" + content + "\n\n" + tip)
}

func helpMenuRender(m model) string {
Expand Down Expand Up @@ -425,5 +426,5 @@ func helpMenuRender(m model) string {

bottomBorder := generateFooterBorder(fmt.Sprintf("%s/%s", strconv.Itoa(m.helpMenu.cursor+1 - cursorBeenTitleCount), strconv.Itoa(len(m.helpMenu.data)-totalTitleCount)), m.helpMenu.width-2)

return fullScreenStyle(m.fullHeight, m.fullWidth).Render(helpMenuModalBorderStyle(m.helpMenu.height, m.helpMenu.width, bottomBorder).Render(helpMenuContent))
return helpMenuModalBorderStyle(m.helpMenu.height, m.helpMenu.width, bottomBorder).Render(helpMenuContent)
}
168 changes: 168 additions & 0 deletions src/components/over_place.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package components

import (
"bytes"
"strings"

charmansi "github.com/charmbracelet/x/exp/term/ansi"
"github.com/mattn/go-runewidth"
ansi "github.com/muesli/reflow/ansi"
"github.com/muesli/reflow/truncate"
"github.com/muesli/termenv"
)

// whitespace is a whitespace renderer.
type whitespace struct {
style termenv.Style
chars string
}

type WhitespaceOption func(*whitespace)

// Render whitespaces.
func (w whitespace) render(width int) string {
if w.chars == "" {
w.chars = " "
}

r := []rune(w.chars)
j := 0
b := strings.Builder{}

// Cycle through runes and print them into the whitespace.
for i := 0; i < width; {
b.WriteRune(r[j])
j++
if j >= len(r) {
j = 0
}
i += charmansi.StringWidth(string(r[j]))
}

// Fill any extra gaps white spaces. This might be necessary if any runes
// are more than one cell wide, which could leave a one-rune gap.
short := width - charmansi.StringWidth(b.String())
if short > 0 {
b.WriteString(strings.Repeat(" ", short))
}

return w.style.Styled(b.String())
}

// PlaceOverlay places fg on top of bg.
func PlaceOverlay(x, y int, fg, bg string, opts ...WhitespaceOption) string {
fgLines, fgWidth := getLines(fg)
bgLines, bgWidth := getLines(bg)
bgHeight := len(bgLines)
fgHeight := len(fgLines)

if fgWidth >= bgWidth && fgHeight >= bgHeight {
// FIXME: return fg or bg?
return fg
}
// TODO: allow placement outside of the bg box?
x = clamp(x, 0, bgWidth-fgWidth)
y = clamp(y, 0, bgHeight-fgHeight)

ws := &whitespace{}
for _, opt := range opts {
opt(ws)
}

var b strings.Builder
for i, bgLine := range bgLines {
if i > 0 {
b.WriteByte('\n')
}
if i < y || i >= y+fgHeight {
b.WriteString(bgLine)
continue
}

pos := 0
if x > 0 {
left := truncate.String(bgLine, uint(x))
pos = ansi.PrintableRuneWidth(left)
b.WriteString(left)
if pos < x {
b.WriteString(ws.render(x - pos))
pos = x
}
}

fgLine := fgLines[i-y]
b.WriteString(fgLine)
pos += ansi.PrintableRuneWidth(fgLine)

right := cutLeft(bgLine, pos)
bgWidth := ansi.PrintableRuneWidth(bgLine)
rightWidth := ansi.PrintableRuneWidth(right)
if rightWidth <= bgWidth-pos {
b.WriteString(ws.render(bgWidth - rightWidth - pos))
}

b.WriteString(right)
}

return b.String()
}

// cutLeft cuts printable characters from the left.
// This function is heavily based on muesli's ansi and truncate packages.
func cutLeft(s string, cutWidth int) string {
var (
pos int
isAnsi bool
ab bytes.Buffer
b bytes.Buffer
)
for _, c := range s {
var w int
if c == ansi.Marker || isAnsi {
isAnsi = true
ab.WriteRune(c)
if ansi.IsTerminator(c) {
isAnsi = false
if bytes.HasSuffix(ab.Bytes(), []byte("[0m")) {
ab.Reset()
}
}
} else {
w = runewidth.RuneWidth(c)
}

if pos >= cutWidth {
if b.Len() == 0 {
if ab.Len() > 0 {
b.Write(ab.Bytes())
}
if pos-cutWidth > 1 {
b.WriteByte(' ')
continue
}
}
b.WriteRune(c)
}
pos += w
}
return b.String()
}

func clamp(v, lower, upper int) int {
return min(max(v, lower), upper)
}

// Split a string into lines, additionally returning the size of the widest
// line.
func getLines(s string) (lines []string, widest int) {
lines = strings.Split(s, "\n")

for _, l := range lines {
w := charmansi.StringWidth(l)
if widest < w {
widest = w
}
}

return lines, widest
}
44 changes: 43 additions & 1 deletion src/components/string_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,46 @@ func clipboardPrettierName(name string, width int, isDir bool, isSelected bool)
Render(style.icon + " ") +
filePanelStyle.Render(truncateTextBeginning(name, width))
}
}
}

// func placeOverlay(x, y int,background, placeModal string) string {
// lines := strings.Split(placeModal, "\n")
// lines = lines
// re := regexp.MustCompile(`\x1b\[[0-9;]*[mK]`)

// // 示例字符串
// str := "┏A我"

// // 使用 FindAllStringIndex 找出所有匹配的位置
// indexes := re.FindAllStringIndex(str, -1)
// outPutLog(str)
// // 檢查是否找到匹配
// if indexes != nil {
// for _, loc := range indexes {
// loc = mapCoords(str, loc)
// outPutLog(fmt.Sprintf("匹配的開始位置: %d, 結束位置: %d", loc[0], loc[1]))
// }
// } else {
// outPutLog("沒有找到匹配")
// }

// return ""
// }

// func mapCoords(s string, byteCoords []int) (graphemeCoords []int) {
// graphemeCoords = make([]int, 2)
// gr := uniseg.NewGraphemes(s)
// graphemeIndex := -1
// for gr.Next() {
// graphemeIndex++
// a, b := gr.Positions()
// if a == byteCoords[0] {
// graphemeCoords[0] = graphemeIndex
// }
// if b == byteCoords[1] {
// graphemeCoords[1] = graphemeIndex + 1
// break
// }
// }
// return
// }
7 changes: 4 additions & 3 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,26 @@ require (
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/exp/term v0.0.0-20240425164147-ba2a9512b05f
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-runewidth v0.0.15
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.2.1
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
Loading
Loading