Skip to content
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
141 changes: 105 additions & 36 deletions PlatCurses.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,40 @@ Colors &Colors::instance() {
}

short Colors::get(const ColourRGBA &color) {
if (const auto entry = colors.find(color.OpaqueRGB()); entry != colors.end())
if (const auto entry = instance().colors.find(color.OpaqueRGB()); entry != instance().colors.end())
return entry->second;
if (colorOffset + colors.size() >= std::numeric_limits<short>::max()) return COLOR_WHITE;
const short c = colorOffset + colors.size();
init_color(c, color.GetRed() * 1000.0 / 255, color.GetGreen() * 1000.0 / 255,
short i = instance().colorOffset + instance().colors.size();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can overflow if the right-hand-side is greater than short max, right?

// try not to overwrite a default color
if (!instance().usePalette && COLORS > 16 && instance().colorOffset < 16)
i += 16 - instance().colorOffset;
Comment on lines +136 to +137

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. Can it be simplified? My branch had if (!usePalette) i += std::clamp(COLORS, 8, 16); Remember the next line short-circuits an i that exceeds COLORS.

if (i >= COLORS || i >= std::numeric_limits<short>::max()) return COLOR_WHITE;
init_color(i, color.GetRed() * 1000.0 / 255, color.GetGreen() * 1000.0 / 255,
color.GetBlue() * 1000.0 / 255);
colors.emplace(color.OpaqueRGB(), c);
return c;
instance().colors.emplace(color.OpaqueRGB(), i);
return i;
}

short Colors::Pair(const ColourRGBA &fore, const ColourRGBA &back) {
if (!has_colors()) return 0;
short Colors::Pair(attr_t &attrs, const ColourRGBA &fore, const ColourRGBA &back) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't like the silent pass through and modification of an attrs parameter. For me, this will cause hard-to-debug issues. I feel like I either brought this up in another PR, or we discussed this back when Colors::Pair() returned an attr_t instead of a color pair number.

We should probably have a private function that returns WA_REVERSE that we can | to an existing attr_t before calls to Colors::Pair() and mvwchgat() and wattr_set().

// If the terminal is a dumb one without colors, we can work with a "second pair"
// that is the reverse of the current one if the background color to draw is
// white. This applies to all `mvwchgat()` and `wattr_set()` calls.
if (!has_colors()) {
if (!(back.Opaque() == Colors::Black)) attrs ^= WA_REVERSE;
return 0;
}

short fore_id, back_id;

if (fore.OpaqueRGB() == instance().defaultBack && back.OpaqueRGB() == instance().defaultFore) {
fore_id = back_id = -1;
attrs ^= WA_REVERSE;
} else {
fore_id = fore.OpaqueRGB() == instance().defaultFore ? -1 : get(fore);
back_id = back.OpaqueRGB() == instance().defaultBack ? -1 : get(back);
}

const auto pair = std::make_pair(fore_id, back_id);
auto &pairs = instance().pairs;
const auto pair = std::make_pair(instance().get(fore), instance().get(back));
if (const auto entry = pairs.find(pair); entry != pairs.end()) return entry->second;
size_t max_pairs = COLOR_PAIRS;
if (const size_t short_max = std::numeric_limits<short>::max(); short_max < max_pairs)
Expand All @@ -160,11 +180,30 @@ ColourRGBA Colors::Find(const short color) {
return Colors::White;
}

void Colors::DisablePalette() {
instance().usePalette = false;
instance().colors.clear();
instance().pairs.clear();
}

void Colors::SetOffsets(int colorOffset, int pairOffset) {
instance().colorOffset = colorOffset;
instance().pairOffset = pairOffset;
}

void Colors::SetDefaultColors(int fore, int back) {
instance().defaultFore = fore;
instance().defaultBack = back;
}

int Colors::GetDefaultFore() {
return instance().defaultFore;
}

int Colors::GetDefaultBack() {
return instance().defaultBack;
}
Comment on lines +194 to +205

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can have static DefaultFore and DefaultBack members just like the colors Black, Red, etc. Since they're not const, they should be mutable for Colors::SetDefaultColors().

Maybe we can store it as a curses color number and ColorRGBA pair so we don't have to keep doing lookups later and also keep Colors::get() as a private instance method.


// Surface handling.

SurfaceImpl::~SurfaceImpl() noexcept { Release(); }
Expand Down Expand Up @@ -224,12 +263,9 @@ void SurfaceImpl::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
short pair = 0, unused_color, back = COLOR_BLACK;
mvwin_wch(win, y, x, &wch), getcchar(&wch, unused_wch, &attrs, &pair, nullptr);
if (pair > 0) pair_content(pair, &unused_color, &back);
// If the terminal is a dumb one without colors, we can work with a "second pair"
// that is the reverse of the current one if the background color to draw is
// white. This applies to all `mvwchgat()` and `wattr_set()` calls.
if (!(has_colors() || Colors::Find(back).Opaque() == Colors::Black)) attrs |= WA_REVERSE;
mvwchgat(
win, y, x, 1, attrs | WA_UNDERLINE, Colors::Pair(stroke.colour, Colors::Find(back)), nullptr);
if (back < 0) back = Colors::get(ColourRGBA::FromRGB(Colors::GetDefaultBack()));
pair = Colors::Pair(attrs, stroke.colour, Colors::Find(back));
mvwchgat(win, y, x, 1, attrs | WA_UNDERLINE, pair, nullptr);
}
}

Expand All @@ -238,8 +274,11 @@ void SurfaceImpl::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
// normally drawn as polygons are handled in `DrawLineMarker()`.
void SurfaceImpl::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
ColourRGBA &back = fillStroke.fill.colour;
const attr_t attrs = has_colors() ? 0 : WA_REVERSE;
wattr_set(win, attrs, Colors::Pair(back, Colors::White), nullptr); // invert
const ColourRGBA &fore = Colors::GetDefaultFore() >= 0
? ColourRGBA::FromRGB(Colors::GetDefaultFore()) : Colors::White;
attr_t attrs = 0;
short pair = Colors::Pair(attrs, back, fore); // invert
wattr_set(win, attrs, pair, nullptr);
if (pts[0].y < pts[npts - 1].y) // up arrow
mvwaddstr(win, static_cast<int>(pts[0].y), static_cast<int>(pts[npts - 1].x - 2), "▲");
else if (pts[0].y > pts[npts - 1].y) // down arrow
Expand All @@ -256,19 +295,25 @@ void SurfaceImpl::RectangleFrame(PRectangle /*rc*/, Stroke /*stroke*/) {}
// Normally this clears the given portion of the screen with the given background color. In
// some cases however, it can be determined that whitespace is being drawn. If so, draw it
// appropriately instead of clearing the given portion of the screen.
// This can also draw the caret.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out.

void SurfaceImpl::FillRectangle(PRectangle rc, Fill fill) {
if (!win) {
// Drawing to a pixmap, probably the fold margin. Record the color for a later fill.
pixmapColor = fill.colour;
return;
}
const attr_t attrs = has_colors() || fill.colour.Opaque() == Colors::Black ? 0 : WA_REVERSE;
wattr_set(win, attrs, Colors::Pair(Colors::White, fill.colour), nullptr);
attr_t attrs = 0;
const ColourRGBA &fore = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::White;
short pair = Colors::Pair(attrs, fore, fill.colour);
wattr_set(win, attrs, pair, nullptr);
chtype ch = ' ';
if (fabs(rc.left - static_cast<int>(rc.left)) > 0.1) {
// If rc.left is a fractional value (e.g. 4.5) then whitespace dots are being drawn. Draw
// them appropriately.
wattr_set(win, attrs, Colors::Pair(fill.colour, fill.colour), nullptr);
attrs = 0;
short pair = Colors::Pair(attrs, fill.colour, fill.colour);
wattr_set(win, attrs, pair, nullptr);
rc.right = static_cast<int>(rc.right), ch = ACS_BULLET | A_BOLD;
}
for (int y = static_cast<int>(rc.top); y < rc.bottom; y++)
Expand Down Expand Up @@ -305,8 +350,9 @@ void SurfaceImpl::AlphaRectangle(PRectangle rc, XYPOSITION /*cornerSize*/, FillS
short pair = 0, fore = COLOR_WHITE, unused_color;
mvwin_wch(win, y, x, &wch), getcchar(&wch, unused_wch, &attrs, &pair, nullptr);
if (pair > 0) pair_content(pair, &fore, &unused_color);
if (!(has_colors() || fill.Opaque() == Colors::Black)) attrs |= WA_REVERSE;
mvwchgat(win, y, x, 1, attrs, Colors::Pair(Colors::Find(fore), fill), nullptr);
if (fore < 0) fore = Colors::get(ColourRGBA::FromRGB(Colors::GetDefaultFore()));
pair = Colors::Pair(attrs, Colors::Find(fore), fill);
mvwchgat(win, y, x, 1, attrs, pair, nullptr);
}
}

Expand All @@ -326,11 +372,22 @@ void SurfaceImpl::Stadium(PRectangle /*rc*/, FillStroke /*fillStroke*/, Ends /*e
// Only called when drawing indentation guides or during certain drawing operations when double
// buffering is enabled. Since the latter is not supported, assume the former.
void SurfaceImpl::Copy(PRectangle rc, Point /*from*/, Surface &surfaceSource) {
const ColourRGBA &fore = dynamic_cast<SurfaceImpl *>(&surfaceSource)->isIndentGuideHighlight ?
Colors::White :
Colors::Black;
ColourRGBA fore, back;
if (dynamic_cast<SurfaceImpl *>(&surfaceSource)->isIndentGuideHighlight) {
fore = Colors::GetDefaultFore() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultFore()) : Colors::White;
back = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
} else {
fore = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
back = Colors::GetDefaultFore() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultFore()) : Colors::White;
}
if (rc.left - 1 < clip.left) return;
wattr_set(win, 0, Colors::Pair(fore, Colors::Black), nullptr);
attr_t attrs = 0;
short pair = Colors::Pair(attrs, fore, back);
wattr_set(win, attrs, pair, nullptr);
mvwaddch(win, static_cast<int>(rc.top), static_cast<int>(rc.left - 1), '|' | A_BOLD);
}

Expand Down Expand Up @@ -360,8 +417,8 @@ int grapheme_width(const char *s) {
void SurfaceImpl::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION /*ybase*/,
std::string_view text, ColourRGBA fore, ColourRGBA back) {
attr_t attrs = dynamic_cast<const FontImpl *>(font_)->attrs;
if (!(has_colors() || back.Opaque() == Colors::Black)) attrs |= WA_REVERSE;
wattr_set(win, attrs, Colors::Pair(fore, back), nullptr);
short pair = Colors::Pair(attrs, fore, back);
wattr_set(win, attrs, pair, nullptr);
if (rc.left < clip.left) {
// Do not overwrite margin text.
auto clip_chars = static_cast<int>(clip.left - rc.left);
Expand Down Expand Up @@ -413,6 +470,11 @@ void SurfaceImpl::DrawTextTransparent(
pair_content(pair, &unused_color, &back);
else if (attrs & WA_REVERSE) // !has_colors() and white terminal background
back = COLOR_WHITE;
if (back < 0) {
// We cannot pass on attrs, so the background color must be correct.
int c = attrs & WA_REVERSE ? Colors::GetDefaultFore() : Colors::GetDefaultBack();
back = Colors::get(ColourRGBA::FromRGB(c));
}
}
DrawTextNoClip(rc, font_, ybase, text, fore, Colors::Find(back));
}
Expand Down Expand Up @@ -480,9 +542,9 @@ void SurfaceImpl::FlushDrawing() {} // N/A
void SurfaceImpl::DrawLineMarker(
const PRectangle &rcWhole, const Font *fontForCharacter, int tFold, const void *data) {
auto marker = reinterpret_cast<const LineMarker *>(data);
attr_t attr = has_colors() || marker->back.Opaque() == Colors::Black ? 0 : WA_REVERSE;
if (tFold) attr |= WA_BOLD;
wattr_set(win, attr, Colors::Pair(marker->fore, marker->back), nullptr);
attr_t attr = tFold ? WA_BOLD : 0;
short pair = Colors::Pair(attr, marker->fore, marker->back);
wattr_set(win, attr, pair, nullptr);
int top = static_cast<int>(rcWhole.top), left = static_cast<int>(rcWhole.left);
switch (marker->markType) {
case MarkerSymbol::Circle: mvwaddstr(win, top, left, "●"); return;
Expand Down Expand Up @@ -526,17 +588,24 @@ void SurfaceImpl::DrawLineMarker(

// Draws the text representation of a wrap marker.
void SurfaceImpl::DrawWrapMarker(PRectangle rcPlace, bool isEndMarker, ColourRGBA wrapColour) {
wattr_set(win, 0, Colors::Pair(wrapColour, Colors::Black), nullptr);
const ColourRGBA &back = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
attr_t attrs = 0;
short pair = Colors::Pair(attrs, wrapColour, back);
wattr_set(win, attrs, pair, nullptr);
mvwaddstr(
win, static_cast<int>(rcPlace.top), static_cast<int>(rcPlace.left), isEndMarker ? "↩" : "↪");
}

// Draws the text representation of a tab arrow.
void SurfaceImpl::DrawTabArrow(PRectangle rcTab, const ViewStyle &vsDraw) {
const ColourRGBA &fore = vsDraw.ElementColour(Element::WhiteSpace).value_or(Colors::Black);
const ColourRGBA &back = vsDraw.ElementColour(Element::WhiteSpaceBack).value_or(Colors::Black);
const attr_t attr = has_colors() || back.Opaque() == Colors::Black ? 0 : WA_REVERSE;
wattr_set(win, attr, Colors::Pair(fore, back), nullptr);
const ColourRGBA &c = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
const ColourRGBA &fore = vsDraw.ElementColour(Element::WhiteSpace).value_or(c);
const ColourRGBA &back = vsDraw.ElementColour(Element::WhiteSpaceBack).value_or(c);
attr_t attr = 0;
short pair = Colors::Pair(attr, fore, back);
wattr_set(win, attr, pair, nullptr);
for (int i = static_cast<int>(std::max(rcTab.left - 1, clip.left)); i < rcTab.right; i++)
mvwaddch(win, static_cast<int>(rcTab.top), i, '-' | A_BOLD);
char tail = vsDraw.tabDrawMode == TabDrawMode::LongArrow ? '>' : '-';
Expand Down
16 changes: 13 additions & 3 deletions PlatCurses.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,23 +141,33 @@ class Colors {
std::map<int, short> colors; // map of RGB ints to curses color numbers.
std::map<std::pair<short, short>, short> pairs; // map of curses colors to their pair numbers
int colorOffset = 0, pairOffset = 0;
bool usePalette = true;
int defaultFore = -1, defaultBack = -1;

Colors();
static Colors &instance();
/** Returns the curses number for a Scintilla color, initializing it if necessary. */
short get(const ColourRGBA &color);

public:
static ColourRGBA Black, Red, Green, Yellow, Blue, Magenta, Cyan, White;
static ColourRGBA LBlack, LRed, LGreen, LYellow, LBlue, LMagenta, LCyan, LWhite;

/** Returns the curses number for a Scintilla color, initializing it if necessary. */
static short get(const ColourRGBA &color);
/** Returns the curses pair number for a Scintilla color pair, initializing it if necessary. */
static short Pair(const ColourRGBA &fore, const ColourRGBA &back);
static short Pair(attr_t &attrs, const ColourRGBA &fore, const ColourRGBA &back);
/** Returns the Scintilla color for a given curses color number. */
static ColourRGBA Find(const short color);

/** Disables use of the terminal's default color palette. */
static void DisablePalette();
/** Sets the offsets for colors and color pairs generated on-demand. */
static void SetOffsets(int colorOffset, int pairOffset);
/** Sets the default foreground and background colors. */
static void SetDefaultColors(int fore, int back);
/** Gets default foreground color as an integer RGB */
static int GetDefaultFore();
/** Gets default background color as an integer RGB */
static int GetDefaultBack();
Comment on lines +166 to +170

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the storage class here because I keep thinking it's a curses color number, not an 0xBBGGRR int. Users may or may not get confused that this needs to be 0xBBGGRR instead of 0xRRGGBB. There might be room for improvement here.

};

inline WINDOW *_WINDOW(WindowID wid) { return reinterpret_cast<WINDOW *>(wid); }
Expand Down
40 changes: 33 additions & 7 deletions ScintillaCurses.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,21 @@ void ScintillaCurses::SetVerticalScrollPos() {
WINDOW *w = GetWINDOW();
int maxy = getmaxy(w), maxx = getmaxx(w);
if (scrollBarHeight == maxy) return; // no point in drawing a non-scrollable bar yet
const ColourRGBA &fore = Colors::GetDefaultFore() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultFore()) : Colors::White;
const ColourRGBA &back = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
// Draw the gutter.
wattr_set(w, 0, Colors::Pair(Colors::White, Colors::Black), nullptr);
attr_t attrs = 0;
short pair = Colors::Pair(attrs, fore, back);
wattr_set(w, attrs, pair, nullptr);
for (int i = 0; i < maxy; i++) mvwaddch(w, i, maxx - 1, ACS_CKBOARD);
// Draw the bar.
scrollBarVPos =
static_cast<int>(static_cast<float>(topLine) / (MaxScrollPos() + LinesOnScreen() - 1) * maxy);
const attr_t attr = has_colors() ? 0 : WA_REVERSE;
wattr_set(w, attr, Colors::Pair(Colors::Black, Colors::White), nullptr); // invert
attrs = 0;
pair = Colors::Pair(attrs, back, fore); // invert
wattr_set(w, attrs, pair, nullptr);
for (int i = scrollBarVPos; i < scrollBarVPos + scrollBarHeight; i++)
mvwaddch(w, i, maxx - 1, ACS_VLINE);
}
Expand All @@ -283,13 +290,20 @@ void ScintillaCurses::SetHorizontalScrollPos() {
WINDOW *w = GetWINDOW();
int maxy = getmaxy(w), maxx = getmaxx(w);
if (scrollBarWidth == maxx) return; // no point in drawing a non-scrollable bar yet
const ColourRGBA &fore = Colors::GetDefaultFore() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultFore()) : Colors::White;
const ColourRGBA &back = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
// Draw the gutter.
wattr_set(w, 0, Colors::Pair(Colors::White, Colors::Black), nullptr);
attr_t attrs = 0;
short pair = Colors::Pair(attrs, fore, back);
wattr_set(w, attrs, pair, nullptr);
for (int i = 0; i < maxx; i++) mvwaddch(w, maxy - 1, i, ACS_CKBOARD);
// Draw the bar.
scrollBarHPos = static_cast<int>(static_cast<float>(xOffset) / scrollWidth * maxx);
const attr_t attr = has_colors() ? 0 : WA_REVERSE;
wattr_set(w, attr, Colors::Pair(Colors::Black, Colors::White), nullptr); // invert
attrs = 0;
pair = Colors::Pair(attrs, back, fore); // invert
wattr_set(w, attrs, pair, nullptr);
for (int i = scrollBarHPos; i < scrollBarHPos + scrollBarWidth; i++)
mvwaddch(w, maxy - 1, i, ACS_HLINE);
}
Expand Down Expand Up @@ -404,7 +418,13 @@ void ScintillaCurses::CreateCallTipWindow(PRectangle rc) {
surface->Init(wid);
dynamic_cast<SurfaceImpl *>(surface.get())->isCallTip = true;
ct.PaintCT(surface.get());
wattr_set(_WINDOW(wid), 0, Colors::Pair(Colors::White, Colors::Black), nullptr);
const ColourRGBA &fore = Colors::GetDefaultFore() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultFore()) : Colors::White;
const ColourRGBA &back = Colors::GetDefaultBack() >= 0 ?
ColourRGBA::FromRGB(Colors::GetDefaultBack()) : Colors::Black;
attr_t attrs = 0;
short pair = Colors::Pair(attrs, fore, back);
wattr_set(_WINDOW(wid), attrs, pair, nullptr);
box(_WINDOW(wid), '|', '-');
wnoutrefresh(_WINDOW(wid));
}
Expand Down Expand Up @@ -699,7 +719,13 @@ void scintilla_update_cursor(void *sci) {

void scintilla_delete(void *sci) { delete reinterpret_cast<ScintillaCurses *>(sci); }

void scintilla_disable_color_palette() { Scintilla::Internal::Colors::DisablePalette(); }

void scintilla_set_color_offsets(int color_offset, int pair_offset) {
Scintilla::Internal::Colors::SetOffsets(color_offset, pair_offset);
}

void scintilla_set_default_colors(int fg, int bg) {
Scintilla::Internal::Colors::SetDefaultColors(fg, bg);
}
}
19 changes: 19 additions & 0 deletions ScintillaCurses.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,31 @@ void scintilla_update_cursor(void *sci);
*/
void scintilla_delete(void *sci);

/**
* Disables use of the terminal's default color palette for all Scintilla windows.
* This only needs to be called once, after calling `start_color()`
* and ideally before any calls to `scintilla_new()`.
*/
void scintilla_disable_color_palette(void);

/**
* Sets the offsets for colors and color pairs generated on-demand.
* Applications that define their own colors and color pairs can tell Scinterm where to start from.
* This should be called after `start_color()`.
*/
void scintilla_set_color_offsets(int color_offset, int pair_offset);

/**
* Configure colors which will be mapped to the terminal's default colors
* when used as the given foreground or background color.
* Default colors might depend on the emulator's color scheme.
* This might not be supported in all curses implementations.
* Use -1 to disable a default foreground or background color.
* You must still call first `start_color()`, then `use_default_colors()`
* or `assume_default_colors()` for this to work.
*/
void scintilla_set_default_colors(int fg, int bg);

#define IMAGE_MAX 31

#define SCM_PRESS 1
Expand Down
Loading