Skip to content

Commit a941537

Browse files
committed
Enhance menuconfig UI to match lxdialog appearance
This refines color scheme, layout, and dialog rendering to closely match Linux kernel's lxdialog implementation. Key improvements include blue background for separators and help text, proper shadow windows for dialogs, corrected window sizing and centering, and immediate background fill using bkgd().
1 parent 86f00c5 commit a941537

File tree

1 file changed

+113
-51
lines changed

1 file changed

+113
-51
lines changed

menuconfig.py

Lines changed: 113 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,12 @@
286286
# Precisely matches lxdialog's set_bluetitle_theme() and set_classic_theme()
287287
"linux": """
288288
path=fg:white,bg:blue,bold
289-
separator=fg:black,bg:white
289+
separator=fg:white,bg:blue
290290
list=fg:black,bg:white
291291
selection=fg:white,bg:blue,bold
292292
inv-list=fg:red,bg:white
293293
inv-selection=fg:red,bg:blue,bold
294-
help=fg:black,bg:white
294+
help=fg:white,bg:blue
295295
show-help=fg:black,bg:white
296296
frame=fg:white,bg:blue,bold
297297
body=fg:black,bg:white
@@ -999,6 +999,10 @@ def _init():
999999

10001000
_init_styles()
10011001

1002+
# Set stdscr background to match main list style
1003+
# This ensures areas not covered by subwindows have correct background
1004+
_stdscr.bkgd(' ', _style.get("list", 0))
1005+
10021006
# Hide the cursor
10031007
_safe_curs_set(0)
10041008

@@ -1044,17 +1048,24 @@ def _resize_main():
10441048

10451049
screen_height, screen_width = _stdscr.getmaxyx()
10461050

1047-
_path_win.resize(1, screen_width)
1048-
_top_sep_win.resize(1, screen_width)
1049-
_bot_sep_win.resize(1, screen_width)
1050-
10511051
help_win_height = _SHOW_HELP_HEIGHT if _show_help else \
10521052
len(_MAIN_HELP_LINES)
10531053

1054+
# Screen layout:
1055+
# Row 0: _path_win, Row 1: _top_sep_win, Row 2+: _menu_win
1056+
# Row (2+menu_win_height): _bot_sep_win
1057+
# Row (2+menu_win_height+1): _help_win
1058+
# Total: 1 + 1 + menu_win_height + 1 + help_win_height = screen_height
10541059
menu_win_height = screen_height - help_win_height - 3
1060+
menu_win_width = screen_width
1061+
1062+
_path_win.resize(1, screen_width)
1063+
_top_sep_win.resize(1, menu_win_width)
10551064

10561065
if menu_win_height >= 1:
1057-
_menu_win.resize(menu_win_height, screen_width)
1066+
_menu_win.resize(menu_win_height, menu_win_width)
1067+
_bot_sep_win.resize(1, menu_win_width)
1068+
# _help_win uses full screen width for blue background to extend to right edge
10581069
_help_win.resize(help_win_height, screen_width)
10591070

10601071
_top_sep_win.mvwin(1, 0)
@@ -1363,8 +1374,10 @@ def _draw_main():
13631374
_safe_hline(_top_sep_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
13641375

13651376
# Add the 'mainmenu' text as the title, centered at the top
1377+
# Use _top_sep_win width instead of term_width for correct centering
1378+
top_sep_width = _width(_top_sep_win)
13661379
_safe_addstr(_top_sep_win,
1367-
0, max((term_width - len(_kconf.mainmenu_text))//2, 0),
1380+
0, max((top_sep_width - len(_kconf.mainmenu_text))//2, 0),
13681381
_kconf.mainmenu_text)
13691382

13701383
_top_sep_win.noutrefresh()
@@ -1752,12 +1765,22 @@ def edit_width():
17521765
# Horizontal scroll offset
17531766
hscroll = max(i - edit_width() + 1, 0)
17541767

1768+
# Create shadow windows once
1769+
win_y, win_x = win.getbegyx()
1770+
win_height, win_width = win.getmaxyx()
1771+
bottom_shadow, right_shadow = _create_shadow_windows(win_y, win_x, win_height, win_width)
1772+
17551773
while True:
17561774
# Draw the "main" display with the menu, etc., so that resizing still
17571775
# works properly. This is like a stack of windows, only hardcoded for
17581776
# now.
17591777
_draw_main()
1778+
17601779
_draw_input_dialog(win, title, info_lines, s, i, hscroll)
1780+
1781+
# Refresh shadow windows after dialog window to ensure they're on top
1782+
_refresh_shadow_windows(bottom_shadow, right_shadow)
1783+
17611784
curses.doupdate()
17621785

17631786

@@ -1767,6 +1790,10 @@ def edit_width():
17671790
# Resize the main display too. The dialog floats above it.
17681791
_resize_main()
17691792
_resize_input_dialog(win, title, info_lines)
1793+
# Recreate shadow windows with new dialog size
1794+
win_y, win_x = win.getbegyx()
1795+
win_height, win_width = win.getmaxyx()
1796+
bottom_shadow, right_shadow = _create_shadow_windows(win_y, win_x, win_height, win_width)
17701797

17711798
elif c == "\n":
17721799
_safe_curs_set(0)
@@ -1806,13 +1833,6 @@ def _resize_input_dialog(win, title, info_lines):
18061833
def _draw_input_dialog(win, title, info_lines, s, i, hscroll):
18071834
edit_width = _width(win) - 4
18081835

1809-
# Get window position and size for shadow
1810-
win_y, win_x = win.getbegyx()
1811-
win_height, win_width = win.getmaxyx()
1812-
1813-
# Draw shadow on stdscr first
1814-
_draw_shadow(_stdscr, win_y, win_x, win_height, win_width)
1815-
18161836
win.erase()
18171837

18181838
# Note: Perhaps having a separate window for the input field would be nicer
@@ -1968,10 +1988,20 @@ def _key_dialog(title, text, keys):
19681988

19691989
_resize_key_dialog(win, text)
19701990

1991+
# Create shadow windows once
1992+
win_y, win_x = win.getbegyx()
1993+
win_height, win_width = win.getmaxyx()
1994+
bottom_shadow, right_shadow = _create_shadow_windows(win_y, win_x, win_height, win_width)
1995+
19711996
while True:
19721997
# See _input_dialog()
19731998
_draw_main()
1999+
19742000
_draw_key_dialog(win, title, text)
2001+
2002+
# Refresh shadow windows after dialog window to ensure they're on top
2003+
_refresh_shadow_windows(bottom_shadow, right_shadow)
2004+
19752005
curses.doupdate()
19762006

19772007

@@ -1981,6 +2011,10 @@ def _key_dialog(title, text, keys):
19812011
# Resize the main display too. The dialog floats above it.
19822012
_resize_main()
19832013
_resize_key_dialog(win, text)
2014+
# Recreate shadow windows with new dialog size
2015+
win_y, win_x = win.getbegyx()
2016+
win_height, win_width = win.getmaxyx()
2017+
bottom_shadow, right_shadow = _create_shadow_windows(win_y, win_x, win_height, win_width)
19842018

19852019
elif c == "\x1B": # \x1B = ESC
19862020
return None
@@ -2007,13 +2041,6 @@ def _resize_key_dialog(win, text):
20072041

20082042

20092043
def _draw_key_dialog(win, title, text):
2010-
# Get window position and size for shadow
2011-
win_y, win_x = win.getbegyx()
2012-
win_height, win_width = win.getmaxyx()
2013-
2014-
# Draw shadow on stdscr first
2015-
_draw_shadow(_stdscr, win_y, win_x, win_height, win_width)
2016-
20172044
win.erase()
20182045

20192046
# Draw the frame first
@@ -2060,16 +2087,15 @@ def _button_dialog(title, text, buttons, default_button=0):
20602087
win.mvwin((_height(_stdscr) - win_height)//2,
20612088
(_width(_stdscr) - win_width)//2)
20622089

2090+
# Create shadow windows once
2091+
win_y, win_x = win.getbegyx()
2092+
win_height, win_width = win.getmaxyx()
2093+
bottom_shadow, right_shadow = _create_shadow_windows(win_y, win_x, win_height, win_width)
2094+
20632095
while True:
2064-
# Draw main display behind dialog
2096+
# Draw main display behind dialog (calls noutrefresh on all subwindows)
20652097
_draw_main()
20662098

2067-
# Get window position and size
2068-
win_y, win_x = win.getbegyx()
2069-
win_height, win_width = win.getmaxyx()
2070-
2071-
# Don't draw shadow for now - TODO: Add shadow support later
2072-
20732099
win.erase()
20742100

20752101
# Draw box border with proper colors
@@ -2147,6 +2173,10 @@ def _button_dialog(title, text, buttons, default_button=0):
21472173
_print_button(win, button_label, button_y, button_positions[i], i == selected_button)
21482174

21492175
win.noutrefresh()
2176+
2177+
# Refresh shadow windows after dialog window to ensure they're on top
2178+
_refresh_shadow_windows(bottom_shadow, right_shadow)
2179+
21502180
curses.doupdate()
21512181

21522182
# Handle input
@@ -2158,6 +2188,10 @@ def _button_dialog(title, text, buttons, default_button=0):
21582188
win.resize(win_height, win_width)
21592189
win.mvwin((_height(_stdscr) - win_height)//2,
21602190
(_width(_stdscr) - win_width)//2)
2191+
# Recreate shadow windows with new dialog size
2192+
win_y, win_x = win.getbegyx()
2193+
win_height, win_width = win.getmaxyx()
2194+
bottom_shadow, right_shadow = _create_shadow_windows(win_y, win_x, win_height, win_width)
21612195

21622196
elif c == "\x1B": # ESC
21632197
return None
@@ -2260,37 +2294,64 @@ def _draw_box(win, y, x, height, width, box_attr, border_attr):
22602294
pass # Interior is handled by caller
22612295

22622296

2263-
def _draw_shadow(win, y, x, height, width):
2264-
# Draw shadows along the right and bottom edge to give a more 3D look,
2265-
# matching lxdialog's draw_shadow()
2297+
def _create_shadow_windows(y, x, height, width, right_y_offset=1):
2298+
# Create shadow windows for bottom and right edges
2299+
# Returns tuple of (bottom_shadow_win, right_shadow_win)
2300+
#
2301+
# Based on lxdialog's draw_shadow():
2302+
# - Bottom: at y + height, from x + 2, width chars
2303+
# - Right: from y + right_y_offset to y + height (inclusive), at x + width, 2 chars wide
22662304

2267-
if curses.has_colors():
2268-
# Note: We need to define shadow color if not already defined
2269-
# For now, use a simple approach: draw with shadow style
2270-
try:
2271-
win.attrset(_style.get("shadow", 0))
2272-
except:
2273-
# Fallback: use dimmed/dark color
2274-
win.attrset(curses.A_DIM)
2305+
if not curses.has_colors():
2306+
return None, None
2307+
2308+
try:
2309+
shadow_attr = _style.get("shadow", 0)
22752310

2276-
# Bottom shadow (offset by 2 on x-axis)
2277-
for i in range(width):
2311+
# Bottom shadow window (1 line high, width wide, offset by 2 on x)
2312+
bottom_shadow = None
2313+
if y + height < _height(_stdscr) and x + 2 + width <= _width(_stdscr):
22782314
try:
2279-
ch = win.inch(y + height, x + 2 + i)
2280-
_safe_addch(win, y + height, x + 2 + i, ch & curses.A_CHARTEXT)
2315+
bottom_shadow = curses.newwin(1, width, y + height, x + 2)
2316+
bottom_shadow.bkgd(' ', shadow_attr)
22812317
except:
22822318
pass
22832319

2284-
# Right shadow (2 characters wide)
2285-
for i in range(1, height + 1):
2320+
# Right shadow window
2321+
# lxdialog: for (i = y + 1; i < y + height + 1; i++)
2322+
# Draw from y+right_y_offset to y+height (inclusive)
2323+
right_shadow = None
2324+
if x + width + 2 <= _width(_stdscr) and y + height <= _height(_stdscr):
22862325
try:
2287-
ch1 = win.inch(y + i, x + width)
2288-
_safe_addch(win, y + i, x + width, ch1 & curses.A_CHARTEXT)
2289-
ch2 = win.inch(y + i, x + width + 1)
2290-
_safe_addch(win, y + i, x + width + 1, ch2 & curses.A_CHARTEXT)
2326+
# From (y + right_y_offset) to (y + height) inclusive
2327+
# Total rows = (y + height) - (y + right_y_offset) + 1 = height - right_y_offset + 1
2328+
shadow_height = height - right_y_offset + 1
2329+
if shadow_height > 0:
2330+
right_shadow = curses.newwin(shadow_height, 2, y + right_y_offset, x + width)
2331+
right_shadow.bkgd(' ', shadow_attr)
22912332
except:
22922333
pass
22932334

2335+
return bottom_shadow, right_shadow
2336+
except:
2337+
return None, None
2338+
2339+
2340+
def _refresh_shadow_windows(bottom_shadow, right_shadow):
2341+
# Refresh shadow windows, refilling them each time to ensure visibility
2342+
if bottom_shadow:
2343+
try:
2344+
bottom_shadow.erase() # Clear and refill with background
2345+
bottom_shadow.noutrefresh()
2346+
except:
2347+
pass
2348+
if right_shadow:
2349+
try:
2350+
right_shadow.erase() # Clear and refill with background
2351+
right_shadow.noutrefresh()
2352+
except:
2353+
pass
2354+
22942355

22952356
def _draw_frame(win, title):
22962357
# Draw a frame around the inner edges of 'win', with 'title' at the top
@@ -3147,8 +3208,9 @@ def _styled_win(style):
31473208

31483209
def _set_style(win, style):
31493210
# Changes the style of an existing window
3211+
# Use bkgd() to immediately fill window with background
31503212

3151-
win.bkgdset(" ", _style[style])
3213+
win.bkgd(" ", _style[style])
31523214

31533215

31543216
def _max_scroll(lst, win):

0 commit comments

Comments
 (0)