mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-10-05 19:08:25 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bc3ee2821 | ||
|
|
2fd1861339 | ||
|
|
b22ce7cbf7 | ||
|
|
0648e7a761 |
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import tea "charm.land/bubbletea/v2"
|
||||||
|
|
||||||
|
type QuitMsg struct{}
|
||||||
|
|
||||||
|
func Quit() tea.Cmd {
|
||||||
|
return func() tea.Msg { return QuitMsg{} }
|
||||||
|
}
|
||||||
|
|
||||||
|
type FocusMsg struct{}
|
||||||
|
|
||||||
|
func Focus() tea.Cmd {
|
||||||
|
return func() tea.Msg { return FocusMsg{} }
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlurMsg struct{}
|
||||||
|
|
||||||
|
func Blur() tea.Cmd {
|
||||||
|
return func() tea.Msg { return BlurMsg{} }
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestFocusMsg struct{}
|
||||||
|
|
||||||
|
func RequestFocus() tea.Cmd {
|
||||||
|
return func() tea.Msg { return RequestFocusMsg{} }
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetTitleMsg struct{ Title string }
|
||||||
|
|
||||||
|
func SetTitle(title string) tea.Cmd {
|
||||||
|
return func() tea.Msg { return SetTitleMsg{Title: title} }
|
||||||
|
}
|
||||||
+1
-5
@@ -43,11 +43,7 @@ func (m Model) renderOne(d Drawer, background string, w, h int) string {
|
|||||||
x = max(w-bw, 0)
|
x = max(w-bw, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
compositor := lipgloss.NewCompositor(
|
return style.Overlay(dim(background, s.DimColor), box, x, 0)
|
||||||
lipgloss.NewLayer(dim(background, s.DimColor)),
|
|
||||||
lipgloss.NewLayer(box).X(x).Y(0).Z(1),
|
|
||||||
)
|
|
||||||
return compositor.Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dim(s string, c color.Color) string {
|
func dim(s string, c color.Color) string {
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package minsize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
|
||||||
|
"github.com/anotherhadi/ilovetui/style"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
MinWidth int
|
||||||
|
MinHeight int
|
||||||
|
Style lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Model)
|
||||||
|
|
||||||
|
func WithStyle(s lipgloss.Style) Option {
|
||||||
|
return func(m *Model) { m.Style = s }
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(minWidth, minHeight int, opts ...Option) Model {
|
||||||
|
m := Model{
|
||||||
|
MinWidth: minWidth,
|
||||||
|
MinHeight: minHeight,
|
||||||
|
Style: lipgloss.NewStyle().Foreground(style.S.Muted),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) Fits(width, height int) bool {
|
||||||
|
return width >= m.MinWidth && height >= m.MinHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) View(width, height int) string {
|
||||||
|
msg := m.Style.Render(m.message(width, height))
|
||||||
|
if width < 1 || height < 1 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) message(width, height int) string {
|
||||||
|
tooNarrow := width < m.MinWidth
|
||||||
|
tooShort := height < m.MinHeight
|
||||||
|
switch {
|
||||||
|
case tooNarrow && tooShort:
|
||||||
|
return fmt.Sprintf("minimum is %dx%d", m.MinWidth, m.MinHeight)
|
||||||
|
case tooNarrow:
|
||||||
|
return fmt.Sprintf("minimum width: %d", m.MinWidth)
|
||||||
|
case tooShort:
|
||||||
|
return fmt.Sprintf("minimum height: %d", m.MinHeight)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-5
@@ -42,11 +42,7 @@ func (m Model) renderOne(mo Modal, background string, w, h int) string {
|
|||||||
bw, bh := lipgloss.Width(box), lipgloss.Height(box)
|
bw, bh := lipgloss.Width(box), lipgloss.Height(box)
|
||||||
x, y := max((w-bw)/2, 0), max((h-bh)/2, 0)
|
x, y := max((w-bw)/2, 0), max((h-bh)/2, 0)
|
||||||
|
|
||||||
compositor := lipgloss.NewCompositor(
|
return style.Overlay(dim(background, s.DimColor), box, x, y)
|
||||||
lipgloss.NewLayer(dim(background, s.DimColor)),
|
|
||||||
lipgloss.NewLayer(box).X(x).Y(y).Z(1),
|
|
||||||
)
|
|
||||||
return compositor.Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dim(s string, c color.Color) string {
|
func dim(s string, c color.Color) string {
|
||||||
|
|||||||
@@ -24,11 +24,7 @@ func (m Model) Render(background string) string {
|
|||||||
sw, sh := lipgloss.Width(stack), lipgloss.Height(stack)
|
sw, sh := lipgloss.Width(stack), lipgloss.Height(stack)
|
||||||
x, y := placement(m.position, w, h, sw, sh)
|
x, y := placement(m.position, w, h, sw, sh)
|
||||||
|
|
||||||
compositor := lipgloss.NewCompositor(
|
return style.Overlay(background, stack, x, y)
|
||||||
lipgloss.NewLayer(background),
|
|
||||||
lipgloss.NewLayer(stack).X(x).Y(y).Z(1),
|
|
||||||
)
|
|
||||||
return compositor.Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) View(width, height int) string {
|
func (m Model) View(width, height int) string {
|
||||||
|
|||||||
@@ -23,6 +23,25 @@ func resolveBorderType(name string) lipgloss.Border {
|
|||||||
return lipgloss.RoundedBorder()
|
return lipgloss.RoundedBorder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LayoutBorderMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LayoutBorderFull LayoutBorderMode = "full"
|
||||||
|
LayoutBorderSidebar LayoutBorderMode = "sidebar"
|
||||||
|
LayoutBorderSeparator LayoutBorderMode = "separator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveLayoutBorder(name string) LayoutBorderMode {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(name)) {
|
||||||
|
case string(LayoutBorderSidebar):
|
||||||
|
return LayoutBorderSidebar
|
||||||
|
case string(LayoutBorderSeparator):
|
||||||
|
return LayoutBorderSeparator
|
||||||
|
default:
|
||||||
|
return LayoutBorderFull
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ContentHeight(totalH int) int {
|
func ContentHeight(totalH int) int {
|
||||||
h := totalH - 2
|
h := totalH - 2
|
||||||
if h < 0 {
|
if h < 0 {
|
||||||
@@ -62,3 +81,37 @@ func RenderWithTitle(border lipgloss.Style, title, content string, width, height
|
|||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, topLine, box)
|
return lipgloss.JoinVertical(lipgloss.Left, topLine, box)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderPlain(border lipgloss.Style, title, content string, width, height int) string {
|
||||||
|
if titleW := lipgloss.Width(title); titleW > width {
|
||||||
|
title = ansi.Truncate(title, width, "")
|
||||||
|
}
|
||||||
|
titleLine := lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(border.GetBorderTopForeground()).
|
||||||
|
Width(width).
|
||||||
|
Render(title)
|
||||||
|
|
||||||
|
contentH := max(height-1, 0)
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
if len(lines) > contentH {
|
||||||
|
content = strings.Join(lines[:contentH], "\n")
|
||||||
|
}
|
||||||
|
box := lipgloss.NewStyle().Width(width).Height(contentH).Render(content)
|
||||||
|
|
||||||
|
return lipgloss.JoinVertical(lipgloss.Left, titleLine, box)
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerticalDivider(border lipgloss.Style, height int) string {
|
||||||
|
b, _, _, _, _ := border.GetBorder()
|
||||||
|
ch := b.Left
|
||||||
|
if ch == "" {
|
||||||
|
ch = "│"
|
||||||
|
}
|
||||||
|
line := lipgloss.NewStyle().Foreground(border.GetBorderLeftForeground()).Render(ch)
|
||||||
|
rows := make([]string, max(height, 0))
|
||||||
|
for i := range rows {
|
||||||
|
rows[i] = line
|
||||||
|
}
|
||||||
|
return strings.Join(rows, "\n")
|
||||||
|
}
|
||||||
|
|||||||
+7
-5
@@ -20,9 +20,10 @@ type colorsYAML struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type configYAML struct {
|
type configYAML struct {
|
||||||
Colors colorsYAML `yaml:"colors"`
|
Colors colorsYAML `yaml:"colors"`
|
||||||
NerdFonts bool `yaml:"nerd_fonts"`
|
NerdFonts bool `yaml:"nerd_fonts"`
|
||||||
Border string `yaml:"border"`
|
Border string `yaml:"border"`
|
||||||
|
LayoutBorder string `yaml:"layout_border"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func pickString(base, user string) string {
|
func pickString(base, user string) string {
|
||||||
@@ -36,8 +37,9 @@ func mergeConfig(base, user configYAML) configYAML {
|
|||||||
return configYAML{
|
return configYAML{
|
||||||
Colors: mergeColors(base.Colors, user.Colors),
|
Colors: mergeColors(base.Colors, user.Colors),
|
||||||
|
|
||||||
NerdFonts: base.NerdFonts || user.NerdFonts,
|
NerdFonts: base.NerdFonts || user.NerdFonts,
|
||||||
Border: pickString(base.Border, user.Border),
|
Border: pickString(base.Border, user.Border),
|
||||||
|
LayoutBorder: pickString(base.LayoutBorder, user.LayoutBorder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ nerd_fonts: false
|
|||||||
# One of: rounded, normal, thick, double, hidden, ascii.
|
# One of: rounded, normal, thick, double, hidden, ascii.
|
||||||
border: rounded
|
border: rounded
|
||||||
|
|
||||||
|
# How sidebar+content layouts draw their borders. One of:
|
||||||
|
# full: both the sidebar and content panes get a full titled border.
|
||||||
|
# sidebar: only the sidebar gets a full border
|
||||||
|
# separator: neither pane is bordered; a single vertical line divides them.
|
||||||
|
layout_border: full
|
||||||
|
|
||||||
colors:
|
colors:
|
||||||
base00: "#110F12" # Background
|
base00: "#110F12" # Background
|
||||||
base01: "#1C1920" # Lighter Background / Status Bars
|
base01: "#1C1920" # Lighter Background / Status Bars
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package style
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/x/ansi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Overlay(base, top string, x, y int) string {
|
||||||
|
baseLines := strings.Split(base, "\n")
|
||||||
|
topLines := strings.Split(top, "\n")
|
||||||
|
|
||||||
|
for i, tl := range topLines {
|
||||||
|
row := y + i
|
||||||
|
if row < 0 || row >= len(baseLines) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bl := baseLines[row]
|
||||||
|
tw := ansi.StringWidth(tl)
|
||||||
|
left := ansi.Cut(bl, 0, x)
|
||||||
|
right := ansi.Cut(bl, x+tw, ansi.StringWidth(bl))
|
||||||
|
baseLines[row] = left + tl + right
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(baseLines, "\n")
|
||||||
|
}
|
||||||
+8
-5
@@ -39,8 +39,9 @@ type Styles struct {
|
|||||||
Bold lipgloss.Style
|
Bold lipgloss.Style
|
||||||
Faint lipgloss.Style
|
Faint lipgloss.Style
|
||||||
|
|
||||||
NerdFonts bool
|
NerdFonts bool
|
||||||
BorderType lipgloss.Border
|
BorderType lipgloss.Border
|
||||||
|
LayoutBorder LayoutBorderMode
|
||||||
|
|
||||||
Panel lipgloss.Style
|
Panel lipgloss.Style
|
||||||
PanelFocused lipgloss.Style
|
PanelFocused lipgloss.Style
|
||||||
@@ -49,7 +50,7 @@ type Styles struct {
|
|||||||
PagerDotInactive string
|
PagerDotInactive string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStyles(c colorsYAML, nerdFonts bool, borderName string) Styles {
|
func newStyles(c colorsYAML, nerdFonts bool, borderName, layoutBorderName string) Styles {
|
||||||
lc := func(s string) color.Color {
|
lc := func(s string) color.Color {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
if s != "" && s[0] != '#' {
|
if s != "" && s[0] != '#' {
|
||||||
@@ -76,6 +77,7 @@ func newStyles(c colorsYAML, nerdFonts bool, borderName string) Styles {
|
|||||||
b0F := lc(c.Base0F)
|
b0F := lc(c.Base0F)
|
||||||
|
|
||||||
borderType := resolveBorderType(borderName)
|
borderType := resolveBorderType(borderName)
|
||||||
|
layoutBorder := resolveLayoutBorder(layoutBorderName)
|
||||||
|
|
||||||
return Styles{
|
return Styles{
|
||||||
Base00: b00, Base01: b01, Base02: b02, Base03: b03,
|
Base00: b00, Base01: b01, Base02: b02, Base03: b03,
|
||||||
@@ -97,8 +99,9 @@ func newStyles(c colorsYAML, nerdFonts bool, borderName string) Styles {
|
|||||||
Bold: lipgloss.NewStyle().Bold(true),
|
Bold: lipgloss.NewStyle().Bold(true),
|
||||||
Faint: lipgloss.NewStyle().Foreground(b03).Faint(true),
|
Faint: lipgloss.NewStyle().Foreground(b03).Faint(true),
|
||||||
|
|
||||||
NerdFonts: nerdFonts,
|
NerdFonts: nerdFonts,
|
||||||
BorderType: borderType,
|
BorderType: borderType,
|
||||||
|
LayoutBorder: layoutBorder,
|
||||||
|
|
||||||
Panel: lipgloss.NewStyle().
|
Panel: lipgloss.NewStyle().
|
||||||
Border(borderType).
|
Border(borderType).
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ func stylesFromBytes(data []byte) (Styles, error) {
|
|||||||
return Styles{}, fmt.Errorf("style: parse config: %w", err)
|
return Styles{}, fmt.Errorf("style: parse config: %w", err)
|
||||||
}
|
}
|
||||||
merged := mergeConfig(base, user)
|
merged := mergeConfig(base, user)
|
||||||
return newStyles(merged.Colors, merged.NerdFonts, merged.Border), nil
|
return newStyles(merged.Colors, merged.NerdFonts, merged.Border, merged.LayoutBorder), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func configDir() string {
|
func configDir() string {
|
||||||
|
|||||||
Reference in New Issue
Block a user