summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/manual/displayattributes.rst18
-rw-r--r--docs/manual/displaymodules.rst2
-rw-r--r--docs/manual/overview.rst12
-rwxr-xr-xexamples/palette_test.py193
-rwxr-xr-xurwid/display_common.py94
-rw-r--r--urwid/raw_display.py20
6 files changed, 293 insertions, 46 deletions
diff --git a/docs/manual/displayattributes.rst b/docs/manual/displayattributes.rst
index 4f800f6..5a69d6d 100644
--- a/docs/manual/displayattributes.rst
+++ b/docs/manual/displayattributes.rst
@@ -7,7 +7,7 @@
.. currentmodule:: urwid
Urwid supports a number of common display attributes in monochrome, 16-color,
-88-color and 256-color modes.
+88-color, 256-color, and 24-bit (true color) modes.
You are encouraged to provide support for as many of these modes as you like, while
allowing your interface to degrade gracefully by providing command line arguments
@@ -293,6 +293,22 @@ specify bold. To inhibit this you can try setting ``bright_is_bold=False`` with
.. _high-colors:
+.. _24-bit-foreground-background:
+
+24-Bit Foreground and Background Colors
+------------------------------------------
+
+In 24-bit color mode, any hex color code of the form #rrggbb can be used to
+specify a precise RGB value for foreground and background colors. Support for
+24-bit color mode varies widely among terminal programs. Furthermore, terminal
+multiplexers such as tmux and screen can sometimes interfere with the operation
+of 24-bit color mode unless properly configured.
+
+.. seealso::
+ The palette_test.py_ example program
+
+.. _palette_test.py: http://excess.org/urwid/browser/examples/palette_test.py
+
.. _256-foreground-background:
256-Color Foreground and Background Colors
diff --git a/docs/manual/displaymodules.rst b/docs/manual/displaymodules.rst
index eba84c8..e87b8fa 100644
--- a/docs/manual/displaymodules.rst
+++ b/docs/manual/displaymodules.rst
@@ -57,7 +57,7 @@ optimized C code no YES
compatible with any terminal no YES [1]_
UTF-8 support YES YES [2]_
bright foreground without bold YES [3]_ no
-88- or 256-color support YES no
+88-/256-/24-bit color support YES no
mouse dragging support YES no
external event loop support YES no
============================== =========== ==============
diff --git a/docs/manual/overview.rst b/docs/manual/overview.rst
index a9e953b..4fd8d6a 100644
--- a/docs/manual/overview.rst
+++ b/docs/manual/overview.rst
@@ -33,12 +33,12 @@ comes with a configurable :ref:`text layout <text-layout>` that handles the
most of the common alignment and wrapping modes. If you need more flexibility
you can also write your own text layout classes.
-Urwid supports a range of common :ref:`display attributes
-<display-attributes>`, including 256-color foreground and background settings,
-bold, underline and standout settings for displaying text. Not all of these
-are supported by all terminals, so Urwid helps you write applications that
-support different color modes depending on what the user's terminal supports
-and what they choose to enable.
+Urwid supports a range of common :ref:`display attributes <display-attributes>`,
+including 24-bit and 256-color foreground and background settings, bold,
+underline and standout settings for displaying text. Not all of these are
+supported by all terminals, so Urwid helps you write applications that support
+different color modes depending on what the user's terminal supports and what
+they choose to enable.
:class:`ListBox` is one of Urwid's most powerful widgets,
and you may control of the :ref:`listbox contents <listbox-contents>` by using
diff --git a/examples/palette_test.py b/examples/palette_test.py
index 0c98623..4fa5d19 100755
--- a/examples/palette_test.py
+++ b/examples/palette_test.py
@@ -21,7 +21,7 @@
"""
Palette test. Shows the available foreground and background settings
-in monochrome, 16 color, 88 color and 256 color modes.
+in monochrome, 16 color, 88 color, 256 color, and 24-bit (true) color modes.
"""
import re
@@ -30,6 +30,170 @@ import sys
import urwid
import urwid.raw_display
+CHART_TRUE="""
+#e50000#e51000#e52000#e53000#e54000#e55000#e56000#e57000#e58000#e59000\
+#e5a000#e5b100#e5c100#e5d100#e5e100#d9e500#c9e500#b9e500#a9e500#99e500\
+#89e500#79e500#68e500#58e500#48e500#38e500#28e500#18e500#08e500#00e507\
+#00e517#00e527#00e538#00e548#00e558#00e568#00e578#00e588#00e598#00e5a8\
+#00e5b8#00e5c8#00e5d8#00e1e5#00d1e5#00c1e5#00b1e5#00a1e5#0091e5#0081e5\
+#0071e5#0061e5#0051e5#0040e5#0030e5#0020e5#0010e5#0000e5#0f00e5#1f00e5\
+#2f00e5#3f00e5#4f00e5#5f00e5#7000e5#8000e5#9000e5#a000e5#b000e5#c000e5\
+#d000e5#e000e5#e500da#e500ca#e500b9#e500a9#e50099#e50089
+#da0000#da0f00#da1e00#da2e00#da3d00#da4c00#da5c00#da6b00#da7a00#da8a00\
+#da9900#daa800#dab800#dac700#dad600#cfda00#c0da00#b0da00#a1da00#92da00\
+#82da00#73da00#64da00#54da00#45da00#35da00#26da00#17da00#07da00#00da07\
+#00da16#00da26#00da35#00da44#00da54#00da63#00da72#00da82#00da91#00daa0\
+#00dab0#00dabf#00dace#00d7da#00c8da#00b8da#00a9da#0099da#008ada#007bda\
+#006bda#005cda#004dda#003dda#002eda#001fda#000fda#0000da#0e00da#1e00da\
+#2d00da#3c00da#4c00da#5b00da#6a00da#7a00da#8900da#9800da#a800da#b700da\
+#c600da#d600da#da00cf#da00c0#da00b1#da00a1#da0092#da0083
+#d00000#d00e00#d01d00#d02b00#d03a00#d04800#d05700#d06600#d07400#d08300\
+#d09100#d0a000#d0af00#d0bd00#d0cc00#c5d000#b6d000#a8d000#99d000#8ad000\
+#7cd000#6dd000#5fd000#50d000#41d000#33d000#24d000#16d000#07d000#00d007\
+#00d015#00d024#00d032#00d041#00d04f#00d05e#00d06d#00d07b#00d08a#00d098\
+#00d0a7#00d0b6#00d0c4#00ccd0#00bed0#00afd0#00a1d0#0092d0#0083d0#0075d0\
+#0066d0#0058d0#0049d0#003ad0#002cd0#001dd0#000fd0#0000d0#0e00d0#1c00d0\
+#2b00d0#3900d0#4800d0#5600d0#6500d0#7400d0#8200d0#9100d0#9f00d0#ae00d0\
+#bd00d0#cb00d0#d000c5#d000b7#d000a8#d00099#d0008b#d0007c
+#c50000#c50d00#c51b00#c52900#c53700#c54500#c55300#c56000#c56e00#c57c00\
+#c58a00#c59800#c5a600#c5b300#c5c100#bbc500#adc500#9fc500#91c500#83c500\
+#75c500#68c500#5ac500#4cc500#3ec500#30c500#22c500#15c500#07c500#00c506\
+#00c514#00c522#00c530#00c53e#00c54b#00c559#00c567#00c575#00c583#00c591\
+#00c59e#00c5ac#00c5ba#00c2c5#00b4c5#00a6c5#0098c5#008ac5#007dc5#006fc5\
+#0061c5#0053c5#0045c5#0037c5#002ac5#001cc5#000ec5#0000c5#0d00c5#1b00c5\
+#2800c5#3600c5#4400c5#5200c5#6000c5#6e00c5#7c00c5#8900c5#9700c5#a500c5\
+#b300c5#c100c5#c500bb#c500ad#c5009f#c50092#c50084#c50076
+#ba0000#ba0d00#ba1a00#ba2700#ba3400#ba4100#ba4e00#ba5b00#ba6800#ba7500\
+#ba8200#ba8f00#ba9c00#baaa00#bab700#b0ba00#a3ba00#96ba00#89ba00#7cba00\
+#6fba00#62ba00#55ba00#48ba00#3bba00#2eba00#20ba00#13ba00#06ba00#00ba06\
+#00ba13#00ba20#00ba2d#00ba3a#00ba47#00ba54#00ba61#00ba6e#00ba7c#00ba89\
+#00ba96#00baa3#00bab0#00b7ba#00aaba#009dba#0090ba#0083ba#0076ba#0069ba\
+#005cba#004eba#0041ba#0034ba#0027ba#001aba#000dba#0000ba#0c00ba#1900ba\
+#2600ba#3300ba#4000ba#4e00ba#5b00ba#6800ba#7500ba#8200ba#8f00ba#9c00ba\
+#a900ba#b600ba#ba00b1#ba00a4#ba0097#ba008a#ba007d#ba006f
+#af0000#af0c00#af1800#af2400#af3100#af3d00#af4900#af5600#af6200#af6e00\
+#af7b00#af8700#af9300#afa000#afac00#a6af00#9aaf00#8eaf00#81af00#75af00\
+#69af00#5caf00#50af00#44af00#37af00#2baf00#1faf00#12af00#06af00#00af05\
+#00af12#00af1e#00af2a#00af37#00af43#00af4f#00af5c#00af68#00af74#00af81\
+#00af8d#00af99#00afa6#00adaf#00a0af#0094af#0088af#007baf#006faf#0063af\
+#0056af#004aaf#003eaf#0031af#0025af#0019af#000caf#0000af#0b00af#1800af\
+#2400af#3000af#3d00af#4900af#5500af#6200af#6e00af#7a00af#8700af#9300af\
+#9f00af#ac00af#af00a7#af009a#af008e#af0082#af0075#af0069
+#a50000#a50b00#a51700#a52200#a52e00#a53900#a54500#a55100#a55c00#a56800\
+#a57300#a57f00#a58a00#a59600#a5a200#9ca500#90a500#85a500#79a500#6ea500\
+#62a500#57a500#4ba500#3fa500#34a500#28a500#1da500#11a500#06a500#00a505\
+#00a511#00a51c#00a528#00a533#00a53f#00a54b#00a556#00a562#00a56d#00a579\
+#00a584#00a590#00a59c#00a2a5#0096a5#008ba5#007fa5#0074a5#0068a5#005da5\
+#0051a5#0045a5#003aa5#002ea5#0023a5#0017a5#000ca5#0000a5#0b00a5#1600a5\
+#2200a5#2d00a5#3900a5#4500a5#5000a5#5c00a5#6700a5#7300a5#7e00a5#8a00a5\
+#9600a5#a100a5#a5009c#a50091#a50085#a5007a#a5006e#a50063
+#9a0000#9a0a00#9a1500#9a2000#9a2b00#9a3600#9a4000#9a4b00#9a5600#9a6100\
+#9a6c00#9a7700#9a8100#9a8c00#9a9700#929a00#879a00#7c9a00#719a00#679a00\
+#5c9a00#519a00#469a00#3b9a00#309a00#269a00#1b9a00#109a00#059a00#009a05\
+#009a10#009a1a#009a25#009a30#009a3b#009a46#009a50#009a5b#009a66#009a71\
+#009a7c#009a87#009a91#00979a#008d9a#00829a#00779a#006c9a#00619a#00569a\
+#004c9a#00419a#00369a#002b9a#00209a#00169a#000b9a#00009a#0a009a#15009a\
+#20009a#2a009a#35009a#40009a#4b009a#56009a#61009a#6b009a#76009a#81009a\
+#8c009a#97009a#9a0092#9a0087#9a007d#9a0072#9a0067#9a005c
+#8f0000#8f0a00#8f1400#8f1e00#8f2800#8f3200#8f3c00#8f4600#8f5000#8f5a00\
+#8f6400#8f6e00#8f7800#8f8200#8f8c00#888f00#7e8f00#748f00#698f00#5f8f00\
+#558f00#4b8f00#418f00#378f00#2d8f00#238f00#198f00#0f8f00#058f00#008f04\
+#008f0e#008f18#008f23#008f2d#008f37#008f41#008f4b#008f55#008f5f#008f69\
+#008f73#008f7d#008f87#008d8f#00838f#00798f#006f8f#00658f#005b8f#00508f\
+#00468f#003c8f#00328f#00288f#001e8f#00148f#000a8f#00008f#09008f#13008f\
+#1d008f#27008f#31008f#3c008f#46008f#50008f#5a008f#64008f#6e008f#78008f\
+#82008f#8c008f#8f0088#8f007e#8f0074#8f006a#8f0060#8f0056
+#840000#840900#841200#841b00#842500#842e00#843700#844100#844a00#845300\
+#845d00#846600#846f00#847900#848200#7d8400#748400#6b8400#628400#588400\
+#4f8400#468400#3c8400#338400#2a8400#208400#178400#0e8400#048400#008404\
+#00840d#008417#008420#008429#008433#00843c#008445#00844f#008458#008461\
+#00846a#008474#00847d#008284#007984#007084#006684#005d84#005484#004a84\
+#004184#003884#002e84#002584#001c84#001284#000984#000084#080084#120084\
+#1b0084#240084#2e0084#370084#400084#4a0084#530084#5c0084#660084#6f0084\
+#780084#820084#84007e#840074#84006b#840062#840059#84004f
+#7a0000#7a0800#7a1100#7a1900#7a2200#7a2a00#7a3300#7a3b00#7a4400#7a4d00\
+#7a5500#7a5e00#7a6600#7a6f00#7a7700#737a00#6b7a00#627a00#5a7a00#517a00\
+#487a00#407a00#377a00#2f7a00#267a00#1e7a00#157a00#0d7a00#047a00#007a04\
+#007a0c#007a15#007a1d#007a26#007a2e#007a37#007a40#007a48#007a51#007a59\
+#007a62#007a6a#007a73#00787a#006f7a#00677a#005e7a#00557a#004d7a#00447a\
+#003c7a#00337a#002b7a#00227a#001a7a#00117a#00087a#00007a#08007a#10007a\
+#19007a#21007a#2a007a#33007a#3b007a#44007a#4c007a#55007a#5d007a#66007a\
+#6f007a#77007a#7a0074#7a006b#7a0062#7a005a#7a0051#7a0049
+#6f0000#6f0700#6f0f00#6f1700#6f1f00#6f2700#6f2e00#6f3600#6f3e00#6f4600\
+#6f4e00#6f5500#6f5d00#6f6500#6f6d00#696f00#616f00#596f00#526f00#4a6f00\
+#426f00#3a6f00#326f00#2b6f00#236f00#1b6f00#136f00#0b6f00#046f00#006f03\
+#006f0b#006f13#006f1b#006f23#006f2a#006f32#006f3a#006f42#006f4a#006f51\
+#006f59#006f61#006f69#006d6f#00656f#005e6f#00566f#004e6f#00466f#003e6f\
+#00366f#002f6f#00276f#001f6f#00176f#000f6f#00086f#00006f#07006f#0f006f\
+#17006f#1e006f#26006f#2e006f#36006f#3e006f#46006f#4d006f#55006f#5d006f\
+#65006f#6d006f#6f0069#6f0062#6f005a#6f0052#6f004a#6f0042
+#640000#640700#640e00#641500#641c00#642300#642a00#643100#643800#643f00\
+#644600#644d00#645400#645b00#646200#5f6400#586400#516400#4a6400#436400\
+#3c6400#356400#2e6400#266400#1f6400#186400#116400#0a6400#036400#006403\
+#00640a#006411#006418#00641f#006426#00642d#006434#00643b#006442#006449\
+#006451#006458#00645f#006364#005c64#005464#004d64#004664#003f64#003864\
+#003164#002a64#002364#001c64#001564#000e64#000764#000064#060064#0d0064\
+#140064#1b0064#230064#2a0064#310064#380064#3f0064#460064#4d0064#540064\
+#5b0064#620064#64005f#640058#640051#64004a#640043#64003c
+#590000#590600#590c00#591200#591900#591f00#592500#592c00#593200#593800\
+#593f00#594500#594b00#595100#595800#555900#4e5900#485900#425900#3c5900\
+#355900#2f5900#295900#225900#1c5900#165900#0f5900#095900#035900#005903\
+#005909#00590f#005915#00591c#005922#005928#00592f#005935#00593b#005942\
+#005948#00594e#005955#005859#005259#004b59#004559#003f59#003859#003259\
+#002c59#002659#001f59#001959#001359#000c59#000659#000059#060059#0c0059\
+#120059#180059#1f0059#250059#2b0059#320059#380059#3e0059#450059#4b0059\
+#510059#580059#590055#59004f#590048#590042#59003c#590035
+#4f0000#4f0500#4f0b00#4f1000#4f1600#4f1b00#4f2100#4f2600#4f2c00#4f3100\
+#4f3700#4f3d00#4f4200#4f4800#4f4d00#4b4f00#454f00#3f4f00#3a4f00#344f00\
+#2f4f00#294f00#244f00#1e4f00#194f00#134f00#0d4f00#084f00#024f00#004f02\
+#004f08#004f0d#004f13#004f18#004f1e#004f23#004f29#004f2f#004f34#004f3a\
+#004f3f#004f45#004f4a#004d4f#00484f#00424f#003d4f#00374f#00324f#002c4f\
+#00274f#00214f#001b4f#00164f#00104f#000b4f#00054f#00004f#05004f#0a004f\
+#10004f#16004f#1b004f#21004f#26004f#2c004f#31004f#37004f#3c004f#42004f\
+#47004f#4d004f#4f004b#4f0045#4f0040#4f003a#4f0035#4f002f
+#440000#440400#440900#440e00#441300#441800#441c00#442100#442600#442b00\
+#443000#443400#443900#443e00#444300#404400#3c4400#374400#324400#2d4400\
+#284400#244400#1f4400#1a4400#154400#104400#0c4400#074400#024400#004402\
+#004407#00440b#004410#004415#00441a#00441f#004423#004428#00442d#004432\
+#004437#00443b#004440#004344#003e44#003944#003444#003044#002b44#002644\
+#002144#001c44#001844#001344#000e44#000944#000444#000044#040044#090044\
+#0e0044#130044#170044#1c0044#210044#260044#2b0044#2f0044#340044#390044\
+#3e0044#430044#440041#44003c#440037#440032#44002d#440029
+#390000#390400#390800#390c00#391000#391400#391800#391c00#392000#392400\
+#392800#392c00#393000#393400#393800#363900#323900#2e3900#2a3900#263900\
+#223900#1e3900#1a3900#163900#123900#0e3900#0a3900#063900#023900#003901\
+#003905#00390a#00390e#003912#003916#00391a#00391e#003922#003926#00392a\
+#00392e#003932#003936#003839#003439#003039#002c39#002839#002439#002039\
+#001c39#001839#001439#001039#000c39#000839#000439#000039#030039#070039\
+#0b0039#100039#140039#180039#1c0039#200039#240039#280039#2c0039#300039\
+#340039#380039#390036#390032#39002e#39002a#390026#390022
+#2e0000#2e0300#2e0600#2e0900#2e0d00#2e1000#2e1300#2e1700#2e1a00#2e1d00\
+#2e2000#2e2400#2e2700#2e2a00#2e2e00#2c2e00#292e00#252e00#222e00#1f2e00\
+#1c2e00#182e00#152e00#122e00#0e2e00#0b2e00#082e00#052e00#012e00#002e01\
+#002e04#002e08#002e0b#002e0e#002e12#002e15#002e18#002e1b#002e1f#002e22\
+#002e25#002e29#002e2c#002e2e#002a2e#00272e#00242e#00212e#001d2e#001a2e\
+#00172e#00132e#00102e#000d2e#000a2e#00062e#00032e#00002e#03002e#06002e\
+#09002e#0d002e#10002e#13002e#16002e#1a002e#1d002e#20002e#24002e#27002e\
+#2a002e#2d002e#2e002c#2e0029#2e0026#2e0022#2e001f#2e001c
+#240000#240200#240500#240700#240a00#240c00#240f00#241100#241400#241600\
+#241900#241b00#241e00#242100#242300#222400#1f2400#1d2400#1a2400#182400\
+#152400#132400#102400#0e2400#0b2400#082400#062400#032400#012400#002401\
+#002403#002406#002408#00240b#00240d#002410#002413#002415#002418#00241a\
+#00241d#00241f#002422#002324#002124#001e24#001c24#001924#001624#001424\
+#001124#000f24#000c24#000a24#000724#000524#000224#000024#020024#040024\
+#070024#0a0024#0c0024#0f0024#110024#140024#160024#190024#1b0024#1e0024\
+#200024#230024#240022#24001f#24001d#24001a#240018#240015
+#190000#190100#190300#190500#190700#190800#190a00#190c00#190e00#191000\
+#191100#191300#191500#191700#191900#181900#161900#141900#121900#111900\
+#0f1900#0d1900#0b1900#091900#081900#061900#041900#021900#001900#001900\
+#001902#001904#001906#001908#001909#00190b#00190d#00190f#001910#001912\
+#001914#001916#001918#001919#001719#001519#001319#001119#001019#000e19\
+#000c19#000a19#000919#000719#000519#000319#000119#000019#010019#030019\
+#050019#070019#080019#0a0019#0c0019#0e0019#100019#110019#130019#150019\
+#170019#180019#190018#190016#190014#190012#190011#19000f
+"""
+# raise Exception(CHART_TRUE)
+
CHART_256 = """
brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
yellow_ light_red light_magenta light_blue light_cyan light_green
@@ -83,10 +247,14 @@ yellow_ light_red light_magenta light_blue light_cyan light_green
black_______ dark_gray___ light_gray__ white_______
"""
-ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>[^ \n]+)")
+ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>(?:#[0-9A-Fa-f]{6})|(?:#[0-9A-Fa-f]{3})|(?:[^ \n]+))")
+LONG_ATTR = 7
SHORT_ATTR = 4 # length of short high-colour descriptions which may
# be packed one after the next
+
+
+
def parse_chart(chart, convert):
"""
Convert string chart into text markup with the correct attributes.
@@ -102,20 +270,28 @@ def parse_chart(chart, convert):
entry = match.group('entry')
entry = entry.replace("_", " ")
while entry:
+ if chart == CHART_TRUE and len(entry) == LONG_ATTR:
+ attrtext = convert(entry[:LONG_ATTR])
+ elen = LONG_ATTR
+ else:
+ elen = SHORT_ATTR
+ attrtext = convert(entry[:SHORT_ATTR])
# try the first four characters
- attrtext = convert(entry[:SHORT_ATTR])
if attrtext:
- elen = SHORT_ATTR
- entry = entry[SHORT_ATTR:].strip()
+ entry = entry[elen:].strip()
else: # try the whole thing
attrtext = convert(entry.strip())
assert attrtext, "Invalid palette entry: %r" % entry
elen = len(entry)
entry = ""
attr, text = attrtext
- out.append((attr, text.ljust(elen)))
+ if chart == CHART_TRUE:
+ out.append((attr, u"\u2584"))
+ else:
+ out.append((attr, text.ljust(elen)))
return out
+
def foreground_chart(chart, background, colors):
"""
Create text markup for a foreground colour chart
@@ -190,7 +366,7 @@ def main():
if colors == 1:
lb[chart_offset] = urwid.Divider()
else:
- chart = {16: CHART_16, 88: CHART_88, 256: CHART_256}[colors]
+ chart = {16: CHART_16, 88: CHART_88, 256: CHART_256, 2**24: CHART_TRUE}[colors]
txt = chart_fn(chart, 'default', colors)
lb[chart_offset] = urwid.Text(txt, wrap='clip')
@@ -220,7 +396,8 @@ def main():
mode_rb("Monochrome", 1),
mode_rb("16-Color", 16, True),
mode_rb("88-Color", 88),
- mode_rb("256-Color", 256),]),
+ mode_rb("256-Color", 256),
+ mode_rb("24-bit Color", 2**24),]),
urwid.Pile([
fcs(urwid.RadioButton(chart_radio_buttons,
"Foreground Colors", True, on_chart_change)),
diff --git a/urwid/display_common.py b/urwid/display_common.py
index e447682..ad7a5b3 100755
--- a/urwid/display_common.py
+++ b/urwid/display_common.py
@@ -80,20 +80,23 @@ _COLOR_VALUES_88 = (_BASIC_COLOR_VALUES +
assert len(_COLOR_VALUES_256) == 256
assert len(_COLOR_VALUES_88) == 88
-_FG_COLOR_MASK = 0x000000ff
-_BG_COLOR_MASK = 0x0000ff00
-_FG_BASIC_COLOR = 0x00010000
-_FG_HIGH_COLOR = 0x00020000
-_BG_BASIC_COLOR = 0x00040000
-_BG_HIGH_COLOR = 0x00080000
-_BG_SHIFT = 8
-_HIGH_88_COLOR = 0x00100000
-_STANDOUT = 0x02000000
-_UNDERLINE = 0x04000000
-_BOLD = 0x08000000
-_BLINK = 0x10000000
-_ITALICS = 0x20000000
-_STRIKETHROUGH = 0x40000000
+_FG_COLOR_MASK = 0x000000ffffff
+_BG_COLOR_MASK = 0xffffff000000
+_FG_BASIC_COLOR = 0x1000000000000
+_FG_HIGH_COLOR = 0x2000000000000
+_FG_TRUE_COLOR = 0x4000000000000
+_BG_BASIC_COLOR = 0x8000000000000
+_BG_HIGH_COLOR = 0x10000000000000
+_BG_TRUE_COLOR = 0x20000000000000
+_BG_SHIFT = 24
+_HIGH_88_COLOR = 0x40000000000000
+_HIGH_TRUE_COLOR = 0x80000000000000
+_STANDOUT = 0x100000000000000
+_UNDERLINE = 0x200000000000000
+_BOLD = 0x400000000000000
+_BLINK = 0x800000000000000
+_ITALICS = 0x1000000000000000
+_STRIKETHROUGH = 0x2000000000000000
_FG_MASK = (_FG_COLOR_MASK | _FG_BASIC_COLOR | _FG_HIGH_COLOR |
_STANDOUT | _UNDERLINE | _BLINK | _BOLD | _ITALICS | _STRIKETHROUGH)
_BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR
@@ -229,6 +232,10 @@ def _gray_num_88(gnum):
return _GRAY_START_88 + gnum
+def _color_desc_true(num):
+
+ return "#%06x" %(num)
+
def _color_desc_256(num):
"""
Return a string description of color number num.
@@ -294,6 +301,23 @@ def _color_desc_88(num):
_CUBE_STEPS_88_16[b])
return 'g%d' % _GRAY_STEPS_88_101[num - _GRAY_START_88]
+def _parse_color_true(desc):
+
+ c = _parse_color_256(desc)
+ if c is not None:
+ (r, g, b) = _COLOR_VALUES_256[c]
+ return (r << 16) + (g << 8) + b
+
+ if not desc.startswith("#"):
+ return None
+ if len(desc) == 7:
+ h = desc[1:]
+ return int(h, 16)
+ elif len(desc) == 4:
+ h = "0x%s0%s0%s" %(desc[1], desc[2], desc[3])
+ return int(h, 16)
+ return None
+
def _parse_color_256(desc):
"""
Return a color number for the description desc.
@@ -382,6 +406,8 @@ def _parse_color_88(desc):
>>> _parse_color_88('g#80')
83
"""
+ if len(desc) == 7:
+ desc = desc[0:2] + desc[3] + desc[5]
if len(desc) > 4:
# keep the length within reason before parsing
return None
@@ -490,9 +516,9 @@ class AttrSpec(object):
>>> AttrSpec('#ddb', '#004', 88)
AttrSpec('#ccc', '#000', colors=88)
"""
- if colors not in (1, 16, 88, 256):
+ if colors not in (1, 16, 88, 256, 2**24):
raise AttrSpecError('invalid number of colors (%d).' % colors)
- self._value = 0 | _HIGH_88_COLOR * (colors == 88)
+ self._value = 0 | _HIGH_88_COLOR * (colors == 88) | _HIGH_TRUE_COLOR * (colors == 2**24)
self.foreground = fg
self.background = bg
if self.colors > colors:
@@ -502,9 +528,11 @@ class AttrSpec(object):
foreground_basic = property(lambda s: s._value & _FG_BASIC_COLOR != 0)
foreground_high = property(lambda s: s._value & _FG_HIGH_COLOR != 0)
+ foreground_true = property(lambda s: s._value & _FG_TRUE_COLOR != 0)
foreground_number = property(lambda s: s._value & _FG_COLOR_MASK)
background_basic = property(lambda s: s._value & _BG_BASIC_COLOR != 0)
background_high = property(lambda s: s._value & _BG_HIGH_COLOR != 0)
+ background_true = property(lambda s: s._value & _BG_TRUE_COLOR != 0)
background_number = property(lambda s: (s._value & _BG_COLOR_MASK)
>> _BG_SHIFT)
italics = property(lambda s: s._value & _ITALICS != 0)
@@ -524,6 +552,8 @@ class AttrSpec(object):
return 88
if self._value & (_BG_HIGH_COLOR | _FG_HIGH_COLOR):
return 256
+ if self._value & (_BG_TRUE_COLOR | _FG_TRUE_COLOR):
+ return 2**24
if self._value & (_BG_BASIC_COLOR | _BG_BASIC_COLOR):
return 16
return 1
@@ -542,12 +572,14 @@ class AttrSpec(object):
def _foreground_color(self):
"""Return only the color component of the foreground."""
- if not (self.foreground_basic or self.foreground_high):
+ if not (self.foreground_basic or self.foreground_high or self.foreground_true):
return 'default'
if self.foreground_basic:
return _BASIC_COLORS[self.foreground_number]
if self.colors == 88:
return _color_desc_88(self.foreground_number)
+ if self.colors == 2**24:
+ return _color_desc_true(self.foreground_number)
return _color_desc_256(self.foreground_number)
def _foreground(self):
@@ -579,6 +611,9 @@ class AttrSpec(object):
elif self._value & _HIGH_88_COLOR:
scolor = _parse_color_88(part)
flags |= _FG_HIGH_COLOR
+ elif self._value & _HIGH_TRUE_COLOR:
+ scolor = _parse_color_true(part)
+ flags |= _FG_TRUE_COLOR
else:
scolor = _parse_color_256(part)
flags |= _FG_HIGH_COLOR
@@ -598,12 +633,14 @@ class AttrSpec(object):
def _background(self):
"""Return the background color."""
- if not (self.background_basic or self.background_high):
+ if not (self.background_basic or self.background_high or self.background_true):
return 'default'
if self.background_basic:
return _BASIC_COLORS[self.background_number]
if self._value & _HIGH_88_COLOR:
return _color_desc_88(self.background_number)
+ if self.colors == 2**24:
+ return _color_desc_true(self.background_number)
return _color_desc_256(self.background_number)
def _set_background(self, background):
@@ -616,6 +653,9 @@ class AttrSpec(object):
elif self._value & _HIGH_88_COLOR:
color = _parse_color_88(background)
flags |= _BG_HIGH_COLOR
+ elif self._value & _HIGH_TRUE_COLOR:
+ color = _parse_color_true(background)
+ flags |= _BG_TRUE_COLOR
else:
color = _parse_color_256(background)
flags |= _BG_HIGH_COLOR
@@ -640,19 +680,25 @@ class AttrSpec(object):
>>> AttrSpec('default', 'g92').get_rgb_values()
(None, None, None, 238, 238, 238)
"""
- if not (self.foreground_basic or self.foreground_high):
+ if not (self.foreground_basic or self.foreground_high or self.foreground_true):
vals = (None, None, None)
elif self.colors == 88:
assert self.foreground_number < 88, "Invalid AttrSpec _value"
vals = _COLOR_VALUES_88[self.foreground_number]
+ elif self.colors == 2**24:
+ h = "%06x" %(self.foreground_number)
+ vals = tuple([int(x, 16) for x in [h[0:2], h[2:4], h[4:6]]])
else:
vals = _COLOR_VALUES_256[self.foreground_number]
- if not (self.background_basic or self.background_high):
+ if not (self.background_basic or self.background_high or self.background_true):
return vals + (None, None, None)
elif self.colors == 88:
assert self.background_number < 88, "Invalid AttrSpec _value"
return vals + _COLOR_VALUES_88[self.background_number]
+ elif self.colors == 2**24:
+ h = "%06x" %(self.background_number)
+ return vals + tuple([int(x, 16) for x in [h[0:2], h[2:4], h[4:6]]])
else:
return vals + _COLOR_VALUES_256[self.background_number]
@@ -873,7 +919,7 @@ class BaseScreen(with_metaclass(signals.MetaSignals, object)):
foreground_high = foreground
if background_high is None:
background_high = background
- high_256 = AttrSpec(foreground_high, background_high, 256)
+ high_true = AttrSpec(foreground_high, background_high, 2**24)
# 'hX' where X > 15 are different in 88/256 color, use
# basic colors for 88-color mode if high colors are specified
@@ -882,7 +928,7 @@ class BaseScreen(with_metaclass(signals.MetaSignals, object)):
if not desc.startswith('h'):
return False
if ',' in desc:
- desc = desc.split(',',1)[0]
+ desc = desc.split(',',1)[0]
num = int(desc[1:], 10)
return num > 15
if large_h(foreground_high) or large_h(background_high):
@@ -891,8 +937,8 @@ class BaseScreen(with_metaclass(signals.MetaSignals, object)):
high_88 = AttrSpec(foreground_high, background_high, 88)
signals.emit_signal(self, UPDATE_PALETTE_ENTRY,
- name, basic, mono, high_88, high_256)
- self._palette[name] = (basic, mono, high_88, high_256)
+ name, basic, mono, high_88, high_true)
+ self._palette[name] = (basic, mono, high_88, high_true)
def _test():
diff --git a/urwid/raw_display.py b/urwid/raw_display.py
index 25d6e09..56673bc 100644
--- a/urwid/raw_display.py
+++ b/urwid/raw_display.py
@@ -93,7 +93,7 @@ class Screen(BaseScreen, RealTerminal):
def _on_update_palette_entry(self, name, *attrspecs):
# copy the attribute to a dictionary containing the escape seqences
- a = attrspecs[{16:0,1:1,88:2,256:3}[self.colors]]
+ a = attrspecs[{16:0,1:1,88:2,256:3,2**24:3}[self.colors]]
self._pal_attrspec[name] = a
self._pal_escape[name] = self._attrspec_to_escape(a)
@@ -931,7 +931,9 @@ class Screen(BaseScreen, RealTerminal):
bg = escape.ESC + '[2;%d}' % (a.background_number,)
return fg + bg
- if a.foreground_high:
+ if a.foreground_true:
+ fg = "38;2;%d;%d;%d" %(a.get_rgb_values()[0:3])
+ elif a.foreground_high:
fg = "38;5;%d" % a.foreground_number
elif a.foreground_basic:
if a.foreground_number > 7:
@@ -946,7 +948,9 @@ class Screen(BaseScreen, RealTerminal):
st = ("1;" * a.bold + "3;" * a.italics +
"4;" * a.underline + "5;" * a.blink +
"7;" * a.standout + "9;" * a.strikethrough)
- if a.background_high:
+ if a.background_true:
+ bg = "48;2;%d;%d;%d" %(a.get_rgb_values()[3:6])
+ elif a.background_high:
bg = "48;5;%d" % a.background_number
elif a.background_basic:
if a.background_number > 7:
@@ -1005,15 +1009,19 @@ class Screen(BaseScreen, RealTerminal):
"""
if self.colors == 1:
return
+ elif self.colors == 2**24:
+ colors = 256
+ else:
+ colors = self.colors
def rgb_values(n):
- if self.colors == 16:
+ if colors == 16:
aspec = AttrSpec("h%d"%n, "", 256)
else:
- aspec = AttrSpec("h%d"%n, "", self.colors)
+ aspec = AttrSpec("h%d"%n, "", colors)
return aspec.get_rgb_values()[:3]
- entries = [(n,) + rgb_values(n) for n in range(self.colors)]
+ entries = [(n,) + rgb_values(n) for n in range(min(colors, 256))]
self.modify_terminal_palette(entries)