Skip to content

Commit 9bd5323

Browse files
committed
ui: Make side panels to overlay the center panel in autohide layout.
This commit makes the side panels to overlay the body which avoids squashing and stretching of the message view which improves UX in autohide mode. To do this this self.frame is stored in `main_window` and the `show_panel` functions have been updated to use Overlay. The focus setting in the `show_panel` functions which could be avoided at a later point needed a reordering in the SEARCH keypress which actually makes it the correct order, i.e., * first handle autohide * then handle search Tests updated.
1 parent 06871aa commit 9bd5323

File tree

2 files changed

+115
-35
lines changed

2 files changed

+115
-35
lines changed

tests/ui/test_ui.py

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Callable, List, Optional, Tuple
1+
from typing import Any, Callable, List, Optional, Tuple, Union
22

33
import pytest
44
from pytest_mock import MockerFixture
@@ -82,15 +82,15 @@ def test_set_footer_text_default(self, view: View, mocker: MockerFixture) -> Non
8282

8383
view.set_footer_text()
8484

85-
view._w.footer.set_text.assert_called_once_with(["some help text"])
85+
view.frame.footer.set_text.assert_called_once_with(["some help text"])
8686
view.controller.update_screen.assert_called_once_with()
8787

8888
def test_set_footer_text_specific_text(
8989
self, view: View, text: str = "blah"
9090
) -> None:
9191
view.set_footer_text([text])
9292

93-
view._w.footer.set_text.assert_called_once_with([text])
93+
view.frame.footer.set_text.assert_called_once_with([text])
9494
view.controller.update_screen.assert_called_once_with()
9595

9696
def test_set_footer_text_with_duration(
@@ -105,7 +105,7 @@ def test_set_footer_text_with_duration(
105105

106106
view.set_footer_text([custom_text], duration=duration)
107107

108-
view._w.footer.set_text.assert_has_calls(
108+
view.frame.footer.set_text.assert_has_calls(
109109
[mocker.call([custom_text]), mocker.call(["some help text"])]
110110
)
111111
mock_sleep.assert_called_once_with(duration)
@@ -179,6 +179,7 @@ def just_set_message_view(self: Any) -> None:
179179
title_divider = mocker.patch(MODULE + ".urwid.Divider")
180180
text = mocker.patch(MODULE + ".urwid.Text")
181181
footer_view = mocker.patch(VIEW + ".footer_view")
182+
show_left_panel = mocker.patch(VIEW + ".show_left_panel")
182183

183184
full_name = "Bob James"
184185
@@ -202,7 +203,7 @@ def just_set_message_view(self: Any) -> None:
202203
expected_column_calls = [
203204
mocker.call(
204205
[
205-
(LEFT_WIDTH, view.left_panel),
206+
(TAB_WIDTH, view.left_tab),
206207
("weight", 10, mocker.ANY), # ANY is a center
207208
(TAB_WIDTH, view.right_tab),
208209
],
@@ -225,58 +226,107 @@ def just_set_message_view(self: Any) -> None:
225226
frame.assert_called_once_with(
226227
view.body, col(), focus_part="body", footer=footer_view()
227228
)
229+
assert view.frame == frame()
230+
show_left_panel.assert_called_once_with(visible=True)
228231

229232
@pytest.mark.parametrize("autohide", [True, False])
230-
@pytest.mark.parametrize("visible, width", [(True, LEFT_WIDTH), (False, TAB_WIDTH)])
233+
@pytest.mark.parametrize("visible", [True, False])
234+
@pytest.mark.parametrize(
235+
"expected_overlay_options",
236+
[
237+
(
238+
"left",
239+
None,
240+
"given",
241+
LEFT_WIDTH + 1,
242+
None,
243+
0,
244+
0,
245+
"top",
246+
None,
247+
"relative",
248+
100,
249+
None,
250+
0,
251+
0,
252+
)
253+
],
254+
)
231255
def test_show_left_panel(
232256
self,
233257
mocker: MockerFixture,
234258
view: View,
235259
visible: bool,
236-
width: int,
237260
autohide: bool,
261+
expected_overlay_options: Tuple[Union[str, int, None]],
238262
) -> None:
239-
view.body = mocker.Mock()
240-
view.body.contents = [view.left_panel, mocker.Mock(), mocker.Mock()]
263+
view.frame.body = view.body
241264
view.controller.autohide = autohide
242265

243266
view.show_left_panel(visible=visible)
244267

245268
if autohide:
246269
if visible:
247-
assert view.body.contents[0][0] == view.left_panel
270+
assert view.frame.body.top_w.contents[0][0] == view.left_panel
271+
assert view.frame.body.bottom_w == view.body
272+
assert view.frame.body.contents[1][1] == expected_overlay_options
248273
else:
249-
assert view.body.contents[0][0] == view.left_tab
250-
view.body.options.assert_called_once_with("given", width)
274+
assert view.frame.body.contents[0][0] == view.left_tab
275+
assert view.body.focus_position == 1
251276
else:
252-
view.body.options.assert_not_called()
277+
# No change
278+
assert view.frame.body.contents[0][0] == view.left_tab
253279

254280
@pytest.mark.parametrize("autohide", [True, False])
255281
@pytest.mark.parametrize(
256282
"visible, width", [(True, RIGHT_WIDTH), (False, TAB_WIDTH)]
257283
)
284+
@pytest.mark.parametrize(
285+
"expected_overlay_options",
286+
[
287+
(
288+
"right",
289+
None,
290+
"given",
291+
RIGHT_WIDTH + 1,
292+
None,
293+
0,
294+
0,
295+
"top",
296+
None,
297+
"relative",
298+
100,
299+
None,
300+
0,
301+
0,
302+
)
303+
],
304+
)
258305
def test_show_right_panel(
259306
self,
260307
mocker: MockerFixture,
261308
view: View,
262309
visible: bool,
263310
width: int,
264311
autohide: bool,
312+
expected_overlay_options: Tuple[Union[str, int, None]],
265313
) -> None:
266-
view.body = mocker.Mock()
267-
view.body.contents = [mocker.Mock(), mocker.Mock(), view.right_panel]
314+
view.frame.body = view.body
268315
view.controller.autohide = autohide
269316

270317
view.show_right_panel(visible=visible)
271318

272319
if autohide:
273320
if visible:
274-
assert view.body.contents[2][0] == view.right_panel
321+
assert view.frame.body.top_w.contents[1][0] == view.right_panel
322+
assert view.frame.body.bottom_w == view.body
323+
assert view.frame.body.contents[1][1] == expected_overlay_options
275324
else:
276-
assert view.body.contents[2][0] == view.right_tab
277-
view.body.options.assert_called_once_with("given", width)
325+
assert view.frame.body.contents[0][0] == view.right_tab
326+
assert view.body.focus_position == 1
278327
else:
279-
view.body.options.assert_not_called()
328+
# No change
329+
assert view.frame.body.contents[0][0] == view.right_tab
280330

281331
def test_keypress_normal_mode_navigation(
282332
self,
@@ -356,7 +406,7 @@ def test_keypress_autohide_users(
356406
view.right_panel = mocker.Mock()
357407
size = widget_size(view)
358408
view.controller.is_in_editor_mode = lambda: False
359-
view.body.focus_position = None
409+
view.body.focus_position = 1
360410

361411
view.keypress(size, key)
362412

zulipterminal/ui.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ def set_footer_text(
111111
text = self.get_random_help()
112112
else:
113113
text = text_list
114-
self._w.footer.set_text(text)
115-
self._w.footer.set_attr_map({None: style})
114+
self.frame.footer.set_text(text)
115+
self.frame.footer.set_attr_map({None: style})
116116
self.controller.update_screen()
117117
if duration is not None:
118118
assert duration > 0
@@ -146,7 +146,7 @@ def main_window(self) -> Any:
146146
self.right_panel, self.right_tab = self.right_column_view()
147147
if self.controller.autohide:
148148
body = [
149-
(LEFT_WIDTH, self.left_panel),
149+
(TAB_WIDTH, self.left_tab),
150150
("weight", 10, self.center_panel),
151151
(TAB_WIDTH, self.right_tab),
152152
]
@@ -181,26 +181,56 @@ def main_window(self) -> Any:
181181
]
182182
)
183183

184-
w = urwid.Frame(
184+
self.frame = urwid.Frame(
185185
self.body, title_bar, focus_part="body", footer=self.footer_view()
186186
)
187-
return w
187+
188+
# Show overlayed left panel on startup in autohide mode
189+
self.show_left_panel(visible=True)
190+
191+
return self.frame
188192

189193
def show_left_panel(self, *, visible: bool) -> None:
190194
if not self.controller.autohide:
191195
return
192196

193-
width = LEFT_WIDTH if visible else TAB_WIDTH
194-
widget = self.left_panel if visible else self.left_tab
195-
self.body.contents[0] = (widget, self.body.options("given", width))
197+
if visible:
198+
self.frame.body = urwid.Overlay(
199+
urwid.Columns(
200+
[(LEFT_WIDTH, self.left_panel), (1, urwid.SolidFill("▏"))]
201+
),
202+
self.body,
203+
align="left",
204+
width=LEFT_WIDTH + 1,
205+
valign="top",
206+
height=("relative", 100),
207+
)
208+
else:
209+
self.frame.body = self.body
210+
# FIXME: This can be avoided after fixing the "sacrificing 1st
211+
# unread msg" issue and setting focus_column=1 when initializing.
212+
self.body.focus_position = 1
196213

197214
def show_right_panel(self, *, visible: bool) -> None:
198215
if not self.controller.autohide:
199216
return
200217

201-
width = RIGHT_WIDTH if visible else TAB_WIDTH
202-
widget = self.right_panel if visible else self.right_tab
203-
self.body.contents[2] = (widget, self.body.options("given", width))
218+
if visible:
219+
self.frame.body = urwid.Overlay(
220+
urwid.Columns(
221+
[(1, urwid.SolidFill("▕")), (RIGHT_WIDTH, self.right_panel)]
222+
),
223+
self.body,
224+
align="right",
225+
width=RIGHT_WIDTH + 1,
226+
valign="top",
227+
height=("relative", 100),
228+
)
229+
else:
230+
self.frame.body = self.body
231+
# FIXME: This can be avoided after fixing the "sacrificing 1st
232+
# unread msg" issue and setting focus_column=1 when initializing.
233+
self.body.focus_position = 1
204234

205235
def keypress(self, size: urwid_Box, key: str) -> Optional[str]:
206236
self.model.new_user_input = True
@@ -228,19 +258,19 @@ def keypress(self, size: urwid_Box, key: str) -> Optional[str]:
228258
self.body.focus_col = 1
229259
elif is_command_key("SEARCH_PEOPLE", key):
230260
# Start User Search if not in editor_mode
231-
self.body.focus_position = 2
232-
self.users_view.keypress(size, key)
233261
self.show_left_panel(visible=False)
234262
self.show_right_panel(visible=True)
263+
self.body.focus_position = 2
264+
self.users_view.keypress(size, key)
235265
return key
236266
elif is_command_key("SEARCH_STREAMS", key) or is_command_key(
237267
"SEARCH_TOPICS", key
238268
):
239269
# jump stream search
240-
self.body.focus_position = 0
241-
self.left_panel.keypress(size, key)
242270
self.show_right_panel(visible=False)
243271
self.show_left_panel(visible=True)
272+
self.body.focus_position = 0
273+
self.left_panel.keypress(size, key)
244274
return key
245275
elif is_command_key("OPEN_DRAFT", key):
246276
saved_draft = self.model.session_draft_message()

0 commit comments

Comments
 (0)