From 337e381927d4134e4705ecae65892755dfdeb249 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Fri, 8 Dec 2023 00:12:26 -0800 Subject: [PATCH] fixes #606 Want ColorNone to preserve existing color(s) While here, consolidate the use of the Fill() function from CellBuffer (eliminating redundant code). --- cell.go | 34 +++++++++++++++++++++++++--------- color.go | 21 +++++++++++++++++---- color_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++-- screen.go | 8 +------- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/cell.go b/cell.go index d6fcfec9..f01b113d 100644 --- a/cell.go +++ b/cell.go @@ -1,4 +1,4 @@ -// Copyright 2022 The TCell Authors +// Copyright 2023 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -31,7 +31,7 @@ type cell struct { lock bool } -// CellBuffer represents a two dimensional array of character cells. +// CellBuffer represents a two-dimensional array of character cells. // This is primarily intended for use by Screen implementors; it // contains much of the common code they need. To create one, just // declare a variable of its type; no explicit initialization is necessary. @@ -44,7 +44,9 @@ type CellBuffer struct { } // SetContent sets the contents (primary rune, combining runes, -// and style) for a cell at a given location. +// and style) for a cell at a given location. If the background or +// foreground of the style is set to ColorNone, then the respective +// color is left un changed. func (cb *CellBuffer) SetContent(x int, y int, mainc rune, combc []rune, style Style, ) { @@ -61,6 +63,12 @@ func (cb *CellBuffer) SetContent(x int, y int, c.width = runewidth.RuneWidth(mainc) } c.currMain = mainc + if style.fg == ColorNone { + style.fg = c.currStyle.fg + } + if style.bg == ColorNone { + style.bg = c.currStyle.bg + } c.currStyle = style } } @@ -97,10 +105,9 @@ func (cb *CellBuffer) Invalidate() { } } -// Dirty checks if a character at the given location needs an -// to be refreshed on the physical display. This returns true -// if the cell content is different since the last time it was -// marked clean. +// Dirty checks if a character at the given location needs to be +// refreshed on the physical display. This returns true if the cell +// content is different since the last time it was marked clean. func (cb *CellBuffer) Dirty(x, y int) bool { if x >= 0 && y >= 0 && x < cb.w && y < cb.h { c := &cb.cells[(y*cb.w)+x] @@ -204,12 +211,21 @@ func (cb *CellBuffer) Resize(w, h int) { // Fill fills the entire cell buffer array with the specified character // and style. Normally choose ' ' to clear the screen. This API doesn't // support combining characters, or characters with a width larger than one. +// If either the foreground or background are ColorNone, then the respective +// color is unchanged. func (cb *CellBuffer) Fill(r rune, style Style) { for i := range cb.cells { c := &cb.cells[i] c.currMain = r c.currComb = nil - c.currStyle = style + cs := style + if cs.fg == ColorNone { + cs.fg = c.currStyle.fg + } + if cs.bg == ColorNone { + cs.bg = c.currStyle.bg + } + c.currStyle = cs c.width = 1 } } @@ -224,7 +240,7 @@ func init() { runewidth.DefaultCondition.EastAsianWidth = false } - // For performance reasons, we create a lookup table. However some users + // For performance reasons, we create a lookup table. However, some users // might be more memory conscious. If that's you, set the TCELL_MINIMIZE // environment variable. if os.Getenv("TCELL_MINIMIZE") == "" { diff --git a/color.go b/color.go index 428d9644..face860f 100644 --- a/color.go +++ b/color.go @@ -1,4 +1,4 @@ -// Copyright 2020 The TCell Authors +// Copyright 2023 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -839,6 +839,11 @@ const ( // ColorReset is used to indicate that the color should use the // vanilla terminal colors. (Basically go back to the defaults.) ColorReset = ColorSpecial | iota + + // ColorNone indicates that we should not change the color from + // whatever is already displayed. This can only be used in limited + // circumstances. + ColorNone ) // ColorNames holds the written names of colors. Useful to present a list of @@ -1002,8 +1007,8 @@ func (c Color) IsRGB() bool { return c&(ColorValid|ColorIsRGB) == (ColorValid | ColorIsRGB) } -// CSS returns the CSS hex string ( #ABCDEF ) if valid -// if not a valid color returns empty string +// CSS returns the CSS hex string ( #ABCDEF ) if valid +// if not a valid color returns empty string func (c Color) CSS() string { if !c.Valid() { return "" @@ -1015,13 +1020,21 @@ func (c Color) CSS() string { // W3C name if it has one or the CSS hex string '#ABCDEF' func (c Color) String() string { if !c.Valid() { + switch c { + case ColorNone: + return "none" + case ColorDefault: + return "default" + case ColorReset: + return "reset" + } return "" } return c.Name(true) } // Name returns W3C name or an empty string if no arguments -// if passed true as an argument it will falls back to +// if passed true as an argument it will falls back to // the CSS hex string if no W3C name found '#ABCDEF' func (c Color) Name(css ...bool) string { for name, hex := range ColorNames { diff --git a/color_test.go b/color_test.go index b41c92e2..cdf2532c 100644 --- a/color_test.go +++ b/color_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The TCell Authors +// Copyright 2023 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -46,7 +46,7 @@ func TestColorValues(t *testing.T) { } func TestColorFitting(t *testing.T) { - pal := []Color{} + var pal []Color for i := 0; i < 255; i++ { pal = append(pal, PaletteColor(i)) } @@ -108,6 +108,17 @@ func TestColorNameLookup(t *testing.T) { t.Errorf("TrueColor did not match") } } + + // these colors only have strings (for debugging), you cannot use them to create a color + if ColorNone.String() != "none" { + t.Errorf("ColorNone did not match") + } + if ColorReset.String() != "reset" { + t.Errorf("ColorReset did not match") + } + if ColorDefault.String() != "default" { + t.Errorf("ColorDefault did not match") + } } func TestColorRGB(t *testing.T) { @@ -132,3 +143,34 @@ func TestFromImageColor(t *testing.T) { t.Errorf("%v is not 0x00FFFF", hex) } } + +func TestColorNone(t *testing.T) { + s := mkTestScreen(t, "") + s.Init() + s.SetSize(80, 24) + st := StyleDefault.Foreground(ColorBlack).Background(ColorWhite) + s.Fill(' ', st) + if _, _, s1, _ := s.GetContent(0, 0); s1 != st { + t.Errorf("Wrong style! fg %s bg %s", s1.fg.String(), s1.bg.String()) + } + st2 := st.Foreground(ColorNone).Background(ColorNone) + s.Fill('X', st2) + if _, _, s1, _ := s.GetContent(0, 0); s1 != st { + t.Errorf("Wrong style! fg %s bg %s", s1.fg.String(), s1.bg.String()) + } + red := st.Foreground(ColorRed).Background(ColorNone) + s.SetContent(1, 0, ' ', nil, red) + if _, _, s1, _ := s.GetContent(1, 0); s1 != red.Background(st.bg) { + t.Errorf("Wrong style! fg %s bg %s", s1.fg.String(), s1.bg.String()) + } + if _, _, s1, _ := s.GetContent(0, 0); s1 != st { + t.Errorf("Wrong style! fg %s bg %s", s1.fg.String(), s1.bg.String()) + } + pink := st.Background(ColorPink).Foreground(ColorNone) + s.SetContent(1, 0, ' ', nil, pink) + combined := pink.Foreground(ColorRed) + + if _, _, s1, _ := s.GetContent(1, 0); s1 != combined { + t.Errorf("Wrong style! fg %s bg %s", s1.fg.String(), s1.bg.String()) + } +} diff --git a/screen.go b/screen.go index 75cc277f..6ab27ca9 100644 --- a/screen.go +++ b/screen.go @@ -376,13 +376,7 @@ func (b *baseScreen) Clear() { func (b *baseScreen) Fill(r rune, style Style) { cb := b.GetCells() b.Lock() - for i := range cb.cells { - c := &cb.cells[i] - c.currMain = r - c.currComb = nil - c.currStyle = style - c.width = 1 - } + cb.Fill(r, style) b.Unlock() }