Del: Update
This commit is contained in:
@@ -1,266 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/nebara/nebara-os/internal/model"
|
||||
"github.com/nebara/nebara-os/internal/ui/theme"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// HeaderWidget renders the NEBARA - OS logo and version on the same row.
|
||||
// The "O" character in "OS" is rendered in the logo accent color (#B30D18).
|
||||
// All other characters use the primary foreground (#F5F5F5).
|
||||
//
|
||||
// Terminal limitation: tview.TextView supports color tags for mixed-color
|
||||
// text on a single line, which we use here.
|
||||
type HeaderWidget struct {
|
||||
*tview.TextView
|
||||
version string
|
||||
marginH int
|
||||
}
|
||||
|
||||
// NewHeaderWidget creates the header with the given version string and margin.
|
||||
func NewHeaderWidget(version string, marginH int) *HeaderWidget {
|
||||
tv := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(false)
|
||||
tv.SetBackgroundColor(theme.ColorBackground)
|
||||
|
||||
h := &HeaderWidget{
|
||||
TextView: tv,
|
||||
version: version,
|
||||
marginH: marginH,
|
||||
}
|
||||
h.refresh(80) // initial width estimate; recalculated on draw
|
||||
return h
|
||||
}
|
||||
|
||||
// SetVersion updates the version string and redraws.
|
||||
func (h *HeaderWidget) SetVersion(v string) {
|
||||
h.version = v
|
||||
}
|
||||
|
||||
// SetMarginH updates the horizontal margin.
|
||||
func (h *HeaderWidget) SetMarginH(m int) {
|
||||
h.marginH = m
|
||||
}
|
||||
|
||||
// Refresh recalculates the header text for the current terminal width.
|
||||
func (h *HeaderWidget) Refresh(termWidth int) {
|
||||
h.refresh(termWidth)
|
||||
}
|
||||
|
||||
func (h *HeaderWidget) refresh(termWidth int) {
|
||||
// Logo: "NEBARA - OS" with "O" in accent red.
|
||||
// tview color tag format: [#RRGGBB]
|
||||
logoPlain := "NEBARA - "
|
||||
// "O" accent, "S" back to primary
|
||||
logoColored := fmt.Sprintf(
|
||||
"[%s]%s[%s]O[%s]S",
|
||||
theme.HexPrimary,
|
||||
logoPlain,
|
||||
theme.HexLogoAccent,
|
||||
theme.HexPrimary,
|
||||
)
|
||||
|
||||
versionText := "Version " + h.version
|
||||
|
||||
// The content width between the margins for text layout purposes.
|
||||
// Visible character count for the logo is 11 ("NEBARA - OS").
|
||||
// Visible character count for version: len("Version ") + len(version).
|
||||
logoVisibleLen := len("NEBARA - OS")
|
||||
versionVisibleLen := len(versionText)
|
||||
contentWidth := termWidth - h.marginH*2
|
||||
|
||||
// Number of spaces between logo and version to right-align version.
|
||||
spaces := contentWidth - logoVisibleLen - versionVisibleLen
|
||||
if spaces < 1 {
|
||||
spaces = 1
|
||||
}
|
||||
|
||||
line := logoColored + strings.Repeat(" ", spaces) + versionText
|
||||
h.TextView.SetText(line)
|
||||
}
|
||||
|
||||
// ApplianceInfoWidget renders the appliance metadata block.
|
||||
type ApplianceInfoWidget struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// NewApplianceInfoWidget creates the appliance info display.
|
||||
func NewApplianceInfoWidget(info model.ApplianceInfo) *ApplianceInfoWidget {
|
||||
tv := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(false)
|
||||
tv.SetBackgroundColor(theme.ColorBackground)
|
||||
|
||||
w := &ApplianceInfoWidget{TextView: tv}
|
||||
w.SetInfo(info)
|
||||
return w
|
||||
}
|
||||
|
||||
// SetInfo refreshes the appliance information display.
|
||||
func (w *ApplianceInfoWidget) SetInfo(info model.ApplianceInfo) {
|
||||
// Product title is bold; labels and values use regular weight.
|
||||
// tview bold tag: [::b] ; reset: [::-]
|
||||
text := fmt.Sprintf(
|
||||
"[%s][::b]%s[::-]\n\n[%s]Hostname: %s\n[%s]Address : %s\n[%s]Uptime : %s\n[%s]Status : %s",
|
||||
theme.HexPrimary, info.ProductName,
|
||||
theme.HexPrimary, info.Hostname,
|
||||
theme.HexPrimary, info.Address,
|
||||
theme.HexPrimary, info.Uptime,
|
||||
theme.HexPrimary, info.Status,
|
||||
)
|
||||
w.TextView.SetText(text)
|
||||
}
|
||||
|
||||
// MessageWidget renders an optional severity-classified system message.
|
||||
type MessageWidget struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// NewMessageWidget creates the message widget (initially empty).
|
||||
func NewMessageWidget() *MessageWidget {
|
||||
tv := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(false)
|
||||
tv.SetBackgroundColor(theme.ColorBackground)
|
||||
return &MessageWidget{TextView: tv}
|
||||
}
|
||||
|
||||
// SetMessage updates the displayed message. Pass nil to clear.
|
||||
func (w *MessageWidget) SetMessage(msg *model.SystemMessage) {
|
||||
if msg == nil || !msg.IsVisible() {
|
||||
w.TextView.SetText("")
|
||||
return
|
||||
}
|
||||
|
||||
var hexColor string
|
||||
switch msg.Severity {
|
||||
case model.SeverityWarning:
|
||||
hexColor = theme.HexWarning
|
||||
case model.SeverityError:
|
||||
hexColor = theme.HexError
|
||||
case model.SeverityInfo:
|
||||
hexColor = theme.HexInfo
|
||||
default:
|
||||
hexColor = theme.HexPrimary
|
||||
}
|
||||
|
||||
// "Message : " label in primary color, then the tagged text in severity color.
|
||||
text := fmt.Sprintf("[%s]Message : [%s]%s",
|
||||
theme.HexPrimary, hexColor, tview.Escape(msg.Text))
|
||||
w.TextView.SetText(text)
|
||||
}
|
||||
|
||||
// DescriptionWidget shows the contextual description for the selected menu item.
|
||||
type DescriptionWidget struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// NewDescriptionWidget creates the description area.
|
||||
func NewDescriptionWidget() *DescriptionWidget {
|
||||
tv := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(false).
|
||||
SetWrap(true)
|
||||
tv.SetBackgroundColor(theme.ColorBackground)
|
||||
return &DescriptionWidget{TextView: tv}
|
||||
}
|
||||
|
||||
// SetDescription updates the displayed contextual description.
|
||||
func (w *DescriptionWidget) SetDescription(text string) {
|
||||
w.TextView.SetText(fmt.Sprintf("[%s]%s", theme.HexSecondary, tview.Escape(text)))
|
||||
}
|
||||
|
||||
// FooterWidget renders the keyboard shortcut hint row.
|
||||
type FooterWidget struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// NewFooterWidget creates the keyboard hint footer.
|
||||
func NewFooterWidget() *FooterWidget {
|
||||
tv := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(false)
|
||||
tv.SetBackgroundColor(theme.ColorBackground)
|
||||
|
||||
f := &FooterWidget{TextView: tv}
|
||||
f.Refresh(120)
|
||||
return f
|
||||
}
|
||||
|
||||
// Refresh redraws the footer at the appropriate density for the terminal width.
|
||||
func (f *FooterWidget) Refresh(termWidth int) {
|
||||
if termWidth < 80 {
|
||||
// Compact form.
|
||||
f.TextView.SetText(fmt.Sprintf("[%s][::b]↑↓[-] Nav [::b]↵[-] Sel [::b]Esc[-] Back [::b]F1[-] Help",
|
||||
theme.HexPrimary))
|
||||
} else {
|
||||
f.TextView.SetText(fmt.Sprintf("[%s][::b]↑↓[-] Navigate [::b]Enter[-] [::b]Select[-] [::b]Esc[-] [::b]Back[-] [::b]F1[-] [::b]Help[-]",
|
||||
theme.HexPrimary))
|
||||
}
|
||||
}
|
||||
|
||||
// ModalWidget is a simple centered dialog box for "not implemented" notices.
|
||||
type ModalWidget struct {
|
||||
*tview.Modal
|
||||
}
|
||||
|
||||
// NewNotImplementedModal creates a minimal "Not Implemented" modal.
|
||||
func NewNotImplementedModal(itemLabel string, onDismiss func()) *tview.Modal {
|
||||
msg := fmt.Sprintf("[ %s ]\n\nThis function is not available in the\nNebara OS TUI Prototype v0.1.\n\nPress Enter or Escape to return.", itemLabel)
|
||||
modal := tview.NewModal().
|
||||
SetText(msg).
|
||||
AddButtons([]string{"OK"}).
|
||||
SetDoneFunc(func(_ int, _ string) {
|
||||
if onDismiss != nil {
|
||||
onDismiss()
|
||||
}
|
||||
})
|
||||
modal.SetBackgroundColor(theme.ColorBackground)
|
||||
modal.SetBorderColor(theme.ColorBorder)
|
||||
modal.SetTextColor(theme.ColorSecondary)
|
||||
return modal
|
||||
}
|
||||
|
||||
// HelpModal returns a modal displaying keyboard shortcuts.
|
||||
func HelpModal(onDismiss func()) *tview.Modal {
|
||||
text := "NEBARA OS — KEYBOARD SHORTCUTS\n\n" +
|
||||
"↑ / ↓ Navigate menu items\n" +
|
||||
"Enter Open submenu / confirm\n" +
|
||||
"Esc Return to previous menu\n" +
|
||||
"Home Jump to first item\n" +
|
||||
"End Jump to last item\n" +
|
||||
"F1 Show this help\n" +
|
||||
"Ctrl+C Quit application"
|
||||
modal := tview.NewModal().
|
||||
SetText(text).
|
||||
AddButtons([]string{"Close"}).
|
||||
SetDoneFunc(func(_ int, _ string) {
|
||||
if onDismiss != nil {
|
||||
onDismiss()
|
||||
}
|
||||
})
|
||||
modal.SetBackgroundColor(theme.ColorBackground)
|
||||
modal.SetBorderColor(theme.ColorBorder)
|
||||
modal.SetTextColor(theme.ColorSecondary)
|
||||
return modal
|
||||
}
|
||||
|
||||
// MinSizeWarning renders a compact message when the terminal is too small.
|
||||
func MinSizeWarning(screen tcell.Screen, w, h int) {
|
||||
msg := " Terminal too small. Minimum: 60×20 "
|
||||
style := tcell.StyleDefault.Background(theme.ColorError).Foreground(theme.ColorBackground)
|
||||
x := (w - len(msg)) / 2
|
||||
y := h / 2
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
for i, ch := range msg {
|
||||
screen.SetContent(x+i, y, ch, nil, style)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user