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

(v2) feat: add compat package #419

Open
wants to merge 6 commits into
base: v2-exp
Choose a base branch
from
Open
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
80 changes: 80 additions & 0 deletions compat/color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package compat

import (
"image/color"
"os"

"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/lipgloss/v2"
)

var (
// HasDarkBackground is true if the terminal has a dark background.
HasDarkBackground = func() bool {
hdb, _ := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
return hdb
}()

// Profile is the color profile of the terminal.
Profile = colorprofile.Detect(os.Stdout, os.Environ())
)

// AdaptiveColor provides color options for light and dark backgrounds. The
// appropriate color will be returned at runtime based on the darkness of the
// terminal background color.
//
// Example usage:
//
// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
type AdaptiveColor struct {
Light color.Color
Dark color.Color
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface.
func (c AdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {
if HasDarkBackground {
return c.Dark.RGBA()
}
return c.Light.RGBA()
}

// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color
// profiles. Automatic color degradation will not be performed.
type CompleteColor struct {
TrueColor color.Color
ANSI256 color.Color
ANSI color.Color
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface.
func (c CompleteColor) RGBA() (uint32, uint32, uint32, uint32) {
switch Profile {

Check failure on line 54 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 54 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 54 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 54 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 54 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 54 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)
case colorprofile.TrueColor:
return c.TrueColor.RGBA()
case colorprofile.ANSI256:
return c.ANSI256.RGBA()
case colorprofile.ANSI:
return c.ANSI.RGBA()
}
return lipgloss.NoColor{}.RGBA()
}

// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
// profiles, with separate options for light and dark backgrounds. Automatic
// color degradation will not be performed.
type CompleteAdaptiveColor struct {
Light CompleteColor
Dark CompleteColor
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface.
func (c CompleteAdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {
if HasDarkBackground {
return c.Dark.RGBA()
}
return c.Light.RGBA()
}
21 changes: 21 additions & 0 deletions compat/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Package compat is a compatibility layer for Lip Gloss that provides a way to
// deal with the hassle of setting up a writer. It's impure because it uses
// global variables, is not thread-safe, and only works with the default
// standard I/O streams.
//
// In case you want [os.Stderr] to be used as the default writer, you can set
// both [Writer] and [HasDarkBackground] to use [os.Stderr] with
// the following code:
//
// import (
// "os"
//
// "github.com/charmbracelet/colorprofile"
// "github.com/charmbracelet/lipgloss/v2/impure"
// )
//
// func init() {
// impure.Writer = colorprofile.NewWriter(os.Stderr, os.Environ())
// impure.HasDarkBackground, _ = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
// }
package compat
2 changes: 1 addition & 1 deletion examples/color/bubbletea/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"

tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/v2"
)

// Style definitions.
Expand Down
2 changes: 1 addition & 1 deletion examples/color/standalone/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"fmt"
"os"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/v2"
)

func main() {
Expand Down
144 changes: 144 additions & 0 deletions examples/compat/bubbletea/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package main

import (
"fmt"
"os"

tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/lipgloss/v2/compat"
)

var (
frameColor = compat.AdaptiveColor{Light: lipgloss.Color("#C5ADF9"), Dark: lipgloss.Color("#864EFF")}
textColor = compat.AdaptiveColor{Light: lipgloss.Color("#696969"), Dark: lipgloss.Color("#bdbdbd")}
keywordColor = compat.AdaptiveColor{Light: lipgloss.Color("#37CD96"), Dark: lipgloss.Color("#22C78A")}
inactiveBgColor = compat.AdaptiveColor{Light: lipgloss.Color(0x988F95), Dark: lipgloss.Color(0x978692)}
inactiveFgColor = compat.AdaptiveColor{Light: lipgloss.Color(0xFDFCE3), Dark: lipgloss.Color(0xFBFAE7)}
)

// Style definitions.
type styles struct {
frame,
paragraph,
text,
keyword,
activeButton,
inactiveButton lipgloss.Style
}

// Styles are initialized based on the background color of the terminal.
func newStyles() (s styles) {
// Define some styles. adaptive.Color() can be used to choose the
// appropriate light or dark color based on the detected background color.
s.frame = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(frameColor).
Padding(1, 3).
Margin(1, 3)
s.paragraph = lipgloss.NewStyle().
Width(40).
MarginBottom(1).
Align(lipgloss.Center)
s.text = lipgloss.NewStyle().
Foreground(textColor)
s.keyword = lipgloss.NewStyle().
Foreground(keywordColor).
Bold(true)

s.activeButton = lipgloss.NewStyle().
Padding(0, 3).
Background(lipgloss.Color(0xFF6AD2)). // you can also use octal format for colors, i.e 0xff38ec.
Foreground(lipgloss.Color(0xFFFCC2))
s.inactiveButton = s.activeButton.
Background(inactiveBgColor).
Foreground(inactiveFgColor)
return s
}

type model struct {
styles styles
yes bool
chosen bool
aborted bool
}

func (m model) Init() (tea.Model, tea.Cmd) {
m.yes = true
m.styles = newStyles()
return m, nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q", "esc", "ctrl+c":
m.aborted = true
return m, tea.Quit
case "enter":
m.chosen = true
return m, tea.Quit
case "left", "right", "h", "l":
m.yes = !m.yes
case "y":
m.yes = true
m.chosen = true
return m, tea.Quit
case "n":
m.yes = false
m.chosen = true
return m, tea.Quit
}
}

return m, nil
}

func (m model) View() string {
if m.chosen || m.aborted {
// We're about to exit, so wipe the UI.
return ""
}

var (
s = m.styles
y = "Yes"
n = "No"
)

if m.yes {
y = s.activeButton.Render(y)
n = s.inactiveButton.Render(n)
} else {
y = s.inactiveButton.Render(y)
n = s.activeButton.Render(n)
}

return s.frame.Render(
lipgloss.JoinVertical(lipgloss.Center,
s.paragraph.Render(
s.text.Render("Are you sure you want to eat that ")+
s.keyword.Render("moderatly ripe")+
s.text.Render(" banana?"),
),
y+" "+n,
),
)
}

func main() {
m, err := tea.NewProgram(model{}).Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Uh oh: %v", err)
os.Exit(1)
}

if m := m.(model); m.chosen {
if m.yes {
fmt.Println("Are you sure? It's not ripe yet.")
} else {
fmt.Println("Well, alright. It was probably good, though.")
}
}
}
66 changes: 66 additions & 0 deletions examples/compat/standalone/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This example illustrates how to detect the terminal's background color and
// choose either light or dark colors accordingly when using Lip Gloss in a.
// standalone fashion, i.e. independent of Bubble Tea.
//
// For an example of how to do this in a Bubble Tea program, see the
// 'bubbletea' example.
package main

import (
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/lipgloss/v2/compat"
)

var (
frameColor = compat.AdaptiveColor{Light: lipgloss.Color("#C5ADF9"), Dark: lipgloss.Color("#864EFF")}
textColor = compat.AdaptiveColor{Light: lipgloss.Color("#696969"), Dark: lipgloss.Color("#bdbdbd")}
keywordColor = compat.AdaptiveColor{Light: lipgloss.Color("#37CD96"), Dark: lipgloss.Color("#22C78A")}
inactiveBgColor = compat.AdaptiveColor{Light: lipgloss.Color(0x988F95), Dark: lipgloss.Color(0x978692)}
inactiveFgColor = compat.AdaptiveColor{Light: lipgloss.Color(0xFDFCE3), Dark: lipgloss.Color(0xFBFAE7)}
)

func main() {
// Define some styles. adaptive.Color() can be used to choose the
// appropriate light or dark color based on the detected background color.
frameStyle := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(frameColor).
Padding(1, 3).
Margin(1, 3)
paragraphStyle := lipgloss.NewStyle().
Width(40).
MarginBottom(1).
Align(lipgloss.Center)
textStyle := lipgloss.NewStyle().
Foreground(textColor)
keywordStyle := lipgloss.NewStyle().
Foreground(keywordColor).
Bold(true)

activeButton := lipgloss.NewStyle().
Padding(0, 3).
Background(lipgloss.Color(0xFF6AD2)). // you can also use octal format for colors, i.e 0xff38ec.
Foreground(lipgloss.Color(0xFFFCC2))
inactiveButton := activeButton.
Background(inactiveBgColor).
Foreground(inactiveFgColor)

// Build layout.
text := paragraphStyle.Render(
textStyle.Render("Are you sure you want to eat that ") +
keywordStyle.Render("moderatly ripe") +
textStyle.Render(" banana?"),
)
buttons := activeButton.Render("Yes") + " " + inactiveButton.Render("No")
block := frameStyle.Render(
lipgloss.JoinVertical(lipgloss.Center, text, buttons),
)

// Print the block to stdout. It's important to use Lip Gloss's print
// functions to ensure that colors are downsampled correctly. If output
// isn't a TTY (i.e. we're logging to a file) colors will be stripped
// entirely.
//
// Note that in Bubble Tea downsampling happens automatically.
lipgloss.Println(block)
}
4 changes: 3 additions & 1 deletion examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ replace github.com/charmbracelet/lipgloss/v2/list => ../list

replace github.com/charmbracelet/lipgloss/v2/table => ../table

replace github.com/charmbracelet/lipgloss/v2/compat => ../compat

require (
github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.1.0.20241031200731-4f70d4c680b8
github.com/charmbracelet/colorprofile v0.1.6
github.com/charmbracelet/lipgloss v0.13.1-0.20240822211938-b89f1a3db2a4
github.com/charmbracelet/lipgloss/v2 v2.0.0-20241101153040-904e60506df7
github.com/charmbracelet/ssh v0.0.0-20240401141849-854cddfa2917
github.com/charmbracelet/wish v1.4.0
Expand All @@ -25,6 +26,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/keygen v0.5.0 // indirect
github.com/charmbracelet/lipgloss v0.13.1-0.20240822211938-b89f1a3db2a4 // indirect
github.com/charmbracelet/log v0.4.0 // indirect
github.com/charmbracelet/x/ansi v0.4.2 // indirect
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 // indirect
Expand Down
5 changes: 2 additions & 3 deletions examples/layout/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"os"
"strings"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/x/term"
"github.com/lucasb-eyer/go-colorful"
"github.com/rivo/uniseg"
Expand Down Expand Up @@ -50,7 +50,6 @@ func init() {
}

func main() {

// Style definitions.
var (

Expand Down Expand Up @@ -427,7 +426,7 @@ func applyGradient(base lipgloss.Style, input string, from, to color.Color) stri
// bytes. The rune count would get us closer but there are times, like with
// emojis, where the rune count is greater than the number of actual
// characters.
var g = uniseg.NewGraphemes(input)
g := uniseg.NewGraphemes(input)
var chars []string
for g.Next() {
chars = append(chars, g.Str())
Expand Down
2 changes: 1 addition & 1 deletion examples/table/ansi/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/lipgloss/v2/table"
)

Expand Down
Loading