Commit c06b9d4e authored by liming6's avatar liming6
Browse files

feature 添加组件堆叠方法

parent 93262722
......@@ -40,10 +40,16 @@ func (mh *ModelHeader) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
func (mh *ModelHeader) View() string {
header := fmt.Sprintf("%s (Press h for help or q for quit)\n", mh.t.Format("2006-01-02 15:04:05"))
style := lipgloss.NewStyle().Width(40)
style := lipgloss.NewStyle().Padding(0, 1)
borderStyle := lipgloss.NewStyle().Border(myBorder, true, true, false, true)
hyv := fmt.Sprintf("hytop %s", mh.DCUTopVersion)
drv := fmt.Sprintf("Driver Version: %s", mh.DriverVersion)
smiv := fmt.Sprintf("SMI Version: %s", mh.SMIVersion)
return header + borderStyle.Render(style.Render(hyv)+style.Render(drv)+style.Render(smiv)) + "\n"
hyv := style.Width(18).Render(fmt.Sprintf("hytop: %s", mh.DCUTopVersion))
drv := style.Width(35).Render(fmt.Sprintf("Driver Version: %s", mh.DriverVersion))
smiv := style.Width(24).Render(fmt.Sprintf("SMI Version: %s", mh.SMIVersion))
return header + borderStyle.Render(hyv+drv+smiv) +
`
├───────────────────────────────┬──────────────────────┬──────────────────────┤
│ GPU Name Persistence-M│ Bus-Id Disp.A │ MIG M. Uncorr. ECC │
│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │
`
}
package tui
import (
"sort"
"strconv"
"strings"
"time"
"github.com/NimbleMarkets/ntcharts/canvas/runes"
tchart "github.com/NimbleMarkets/ntcharts/linechart/timeserieslinechart"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
zone "github.com/lrstanley/bubblezone"
)
const (
A = "├"
)
var (
axisFStyle = lipgloss.NewStyle().Inline(true).Foreground(lipgloss.Color("#707070ff"))
)
// genXAxis 生成X轴,参数l是x轴的长度
func genXAxis(l int) string {
t60 := l / 30
t30 := l >= 18
var result string
if t30 {
result = A + strings.Repeat("─", 14)
result = axisFStyle.Render("30s") + result
} else {
return strings.Repeat("─", l)
}
// 长度不超过33
if l < 33 {
return strings.Repeat("─", l-18) + result
}
for i := 1; i <= t60+1; i++ {
timeStr := strconv.Itoa(i*60) + "s"
timeStrLen := len(timeStr)
timeStr = axisFStyle.Render(timeStr)
targetLen := timeStrLen + i*30
if l < targetLen {
// 不渲染标记,仅增加轴长度
result = strings.Repeat("─", l-lipgloss.Width(result)) + result
break
}
// 渲染标记
result = timeStr + A + strings.Repeat("─", targetLen-lipgloss.Width(result)-timeStrLen-1) + result
}
return result
}
// MyTimeChartMsg 时间流表消息,用于插入数据
type MyTimeChartMsg struct {
point tchart.TimePoint
}
// MyTimeChart 特化的时间流表,时间区域就是宽度的2倍,单位是秒
type MyTimeChart struct {
chart *tchart.Model // 原始图表
zM *zone.Manager // 区域管理
points []tchart.TimePoint // 数据点
width, height int // 图表的高度和宽度
max, min float64 // y轴的最值
}
func New(width, height int, vmin, vmax float64) *MyTimeChart {
result := MyTimeChart{}
result.max = vmax
result.min = vmin
result.width = width
result.height = height
zoneManager := zone.New()
result.zM = zoneManager
result.points = make([]tchart.TimePoint, 0)
// 准备数据,数据间隔为1秒
now := time.Now()
for i := result.width * 2; i >= 0; i-- {
result.points = append(result.points, tchart.TimePoint{Time: now.Add(-time.Duration(i) * time.Second), Value: vmin})
}
s := tchart.New(width, height,
tchart.WithLineStyle(runes.ThinLineStyle),
tchart.WithZoneManager(zoneManager),
tchart.WithYRange(vmin, vmax),
tchart.WithTimeSeries(result.points),
tchart.WithXYSteps(0, 0),
)
result.chart = &s
return &result
}
func (m *MyTimeChart) PutPoint(timePoint tchart.TimePoint) {
m.points = append(m.points, timePoint)
// 排序
sort.Slice(m.points, func(i, j int) bool {
return m.points[i].Time.Before(m.points[j].Time)
})
// 剔除不要的数据
threshold := time.Now().Add(time.Second * time.Duration(-2*m.width))
targetIndex := 0
for i, p := range m.points {
if !p.Time.Before(threshold) {
targetIndex = i
break
}
}
points := append(make([]tchart.TimePoint, 0), m.points[targetIndex:]...)
m.points = points
// 更新chart
s := tchart.New(m.width, m.height,
tchart.WithLineStyle(runes.ThinLineStyle),
tchart.WithZoneManager(m.zM),
tchart.WithYRange(m.min, m.max),
tchart.WithTimeSeries(m.points),
tchart.WithXYSteps(0, 0),
)
m.chart = &s
}
func (m *MyTimeChart) Init() tea.Cmd {
m.chart.DrawXYAxisAndLabel()
m.chart.DrawBraille()
return nil
}
func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := inputMsg.(type) {
case MyTimeChartMsg:
m.PutPoint(msg.point)
return m, nil
}
return m, nil
}
func (m *MyTimeChart) View() string {
return m.zM.Scan(m.chart.View())
}
......@@ -2,12 +2,36 @@ package tui
import (
"fmt"
"strings"
"testing"
"github.com/charmbracelet/lipgloss"
)
const Border = `├───────────────────────────────┬──────────────────────┬──────────────────────┤
│ GPU Name Persistence-M│ Bus-Id Disp.A │ MIG M. Uncorr. ECC │
│ Fan Temp Perf Pwr:Usage/Cap│ Memory-Usage │ GPU-Util Compute M. │`
func TestHeader(t *testing.T) {
m := ModelHeader{}
cmd := m.Init()
m.Update(cmd)
fmt.Println(m.View())
}
func TestBorder(t *testing.T) {
lines := strings.Split(Border, "\n")
for _, i := range lines {
t.Logf("%d", len(i))
}
}
func TestAis(t *testing.T) {
for i := 10; i < 180; i++ {
str := genXAxis(i)
if lipgloss.Width(str) != i {
t.Error("error length")
}
fmt.Println(str)
}
}
package tui
import (
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
)
func absInt(a int) int {
if a >= 0 {
return a
}
return -a
}
// StackLine 行堆叠,输出字符串的长度为down的长度,x表示up向右横移的字符数
func StackLine(up, down string, x int) string {
lup, ldown := ansi.StringWidth(up), ansi.StringWidth(down)
if x >= 0 {
if x >= ldown {
return down
}
result := ansi.Truncate(down, x, "")
if x+lup >= ldown {
result += ansi.Truncate(up, ldown-x, "")
} else {
result += up
result += ansi.TruncateLeft(down, x+lup, "")
}
return result
} else {
if absInt(x) >= lup {
return down
}
result := ansi.TruncateLeft(up, absInt(x), "")
l := ansi.StringWidth(result)
if l < ldown {
result += ansi.TruncateLeft(down, lup+x, "")
} else {
result = ansi.Truncate(result, ldown, "")
}
return result
}
}
// Stack 堆叠两个字符串,输出的字符串尺寸与down的尺寸一致
func Stack(up, down string, x, y int) string {
downHeight := lipgloss.Height(down)
downLines := strings.Split(strings.Trim(down, "\n"), "\n")
upLines := strings.Split(strings.Trim(up, "\n"), "\n")
result := make([]string, downHeight)
if y >= 0 {
if y >= downHeight {
result = downLines
} else {
for k, v := range downLines {
if k-y >= 0 && k-y < len(upLines) {
result[k] = StackLine(upLines[k-y], v, x)
} else {
result[k] = downLines[k]
}
}
}
} else {
if absInt(y) > len(upLines) {
result = downLines
} else {
for k, v := range downLines {
if k-y < len(upLines) {
result[k] = StackLine(upLines[k-y], v, x)
} else {
result[k] = downLines[k]
}
}
}
}
r := strings.Join(result, "\n")
if down[len(down)-1] == '\n' {
r += "\n"
}
return r
}
// StackPosition 按照位置堆叠
func StackPosition(up, down string, vPos, hPos lipgloss.Position) string {
upW, upH := lipgloss.Size(up)
downW, downH := lipgloss.Size(down)
switch vPos {
case lipgloss.Top:
switch hPos {
case lipgloss.Left:
// 上左
return Stack(up, down, 0, 0)
case lipgloss.Center:
// 上中
return Stack(up, down, (downW-upW)/2, 0)
case lipgloss.Right:
// 上右
return Stack(up, down, downW-upW, 0)
}
case lipgloss.Center:
switch hPos {
case lipgloss.Left:
// 中左
return Stack(up, down, 0, (downH-upH)/2)
case lipgloss.Center:
// 中中
return Stack(up, down, (downW-upW)/2, (downH-upH)/2)
case lipgloss.Right:
// 中右
return Stack(up, down, downW-upW, (downH-upH)/2)
}
case lipgloss.Bottom:
switch hPos {
case lipgloss.Left:
// 下左
return Stack(up, down, 0, downH-upH)
case lipgloss.Center:
// 下中
return Stack(up, down, (downW-upW)/2, downH-upH)
case lipgloss.Right:
// 下右
return Stack(up, down, downW-upW, downH-upH)
}
}
return ""
}
......@@ -10,9 +10,15 @@ require (
github.com/shirou/gopsutil/v4 v4.25.9
)
require (
github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e // indirect
)
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/NimbleMarkets/ntcharts v0.3.1
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment