urwid 2.6.16__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of urwid might be problematic. Click here for more details.

Files changed (53) hide show
  1. urwid/__init__.py +1 -4
  2. urwid/canvas.py +18 -37
  3. urwid/command_map.py +4 -3
  4. urwid/container.py +1 -1
  5. urwid/decoration.py +1 -1
  6. urwid/display/_raw_display_base.py +8 -5
  7. urwid/display/_win32_raw_display.py +11 -13
  8. urwid/display/common.py +26 -55
  9. urwid/display/curses.py +1 -1
  10. urwid/display/escape.py +6 -8
  11. urwid/display/lcd.py +4 -6
  12. urwid/display/web.py +7 -12
  13. urwid/event_loop/asyncio_loop.py +1 -2
  14. urwid/event_loop/main_loop.py +13 -18
  15. urwid/event_loop/tornado_loop.py +4 -5
  16. urwid/event_loop/trio_loop.py +1 -1
  17. urwid/font.py +10 -15
  18. urwid/signals.py +2 -1
  19. urwid/str_util.py +15 -18
  20. urwid/text_layout.py +6 -7
  21. urwid/util.py +6 -17
  22. urwid/version.py +9 -4
  23. urwid/vterm.py +9 -44
  24. urwid/widget/__init__.py +0 -6
  25. urwid/widget/attr_wrap.py +8 -10
  26. urwid/widget/bar_graph.py +2 -7
  27. urwid/widget/big_text.py +9 -7
  28. urwid/widget/box_adapter.py +4 -4
  29. urwid/widget/columns.py +50 -81
  30. urwid/widget/container.py +29 -75
  31. urwid/widget/edit.py +8 -8
  32. urwid/widget/filler.py +6 -6
  33. urwid/widget/frame.py +28 -37
  34. urwid/widget/grid_flow.py +24 -109
  35. urwid/widget/line_box.py +13 -0
  36. urwid/widget/listbox.py +12 -50
  37. urwid/widget/monitored_list.py +6 -4
  38. urwid/widget/overlay.py +4 -37
  39. urwid/widget/padding.py +11 -48
  40. urwid/widget/pile.py +177 -156
  41. urwid/widget/popup.py +2 -2
  42. urwid/widget/progress_bar.py +0 -10
  43. urwid/widget/scrollable.py +24 -32
  44. urwid/widget/treetools.py +27 -48
  45. urwid/widget/widget.py +7 -124
  46. urwid/widget/widget_decoration.py +4 -33
  47. urwid/wimp.py +1 -1
  48. {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info}/METADATA +9 -11
  49. urwid-3.0.0.dist-info/RECORD +74 -0
  50. {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info}/WHEEL +1 -1
  51. urwid-2.6.16.dist-info/RECORD +0 -74
  52. {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info/licenses}/COPYING +0 -0
  53. {urwid-2.6.16.dist-info → urwid-3.0.0.dist-info}/top_level.txt +0 -0
urwid/widget/pile.py CHANGED
@@ -135,8 +135,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
135
135
 
136
136
  if not flag & box_flow_fixed:
137
137
  warnings.warn(
138
- f"Sizing combination of widget {idx} not supported: "
139
- f"{size_kind.name} {'|'.join(w_sizing).upper()}",
138
+ f"Sizing combination of widget {idx} not supported: {size_kind.name} {'|'.join(w_sizing).upper()}",
140
139
  PileWarning,
141
140
  stacklevel=3,
142
141
  )
@@ -302,8 +301,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
302
301
  standard container property :attr:`contents`.
303
302
  """
304
303
  warnings.warn(
305
- "only for backwards compatibility. You should use the new standard container property `contents`",
306
- PendingDeprecationWarning,
304
+ "only for backwards compatibility. You should use the new standard container property `contents`."
305
+ "API will be removed in version 5.0.",
306
+ DeprecationWarning,
307
307
  stacklevel=2,
308
308
  )
309
309
  ml = MonitoredList(w for w, t in self.contents)
@@ -337,8 +337,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
337
337
  standard container property :attr:`contents`.
338
338
  """
339
339
  warnings.warn(
340
- "only for backwards compatibility. You should use the new standard container property `contents`",
341
- PendingDeprecationWarning,
340
+ "only for backwards compatibility. You should use the new standard container property `contents`."
341
+ "API will be removed in version 5.0.",
342
+ DeprecationWarning,
342
343
  stacklevel=2,
343
344
  )
344
345
  ml = MonitoredList(
@@ -356,8 +357,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
356
357
  @item_types.setter
357
358
  def item_types(self, item_types):
358
359
  warnings.warn(
359
- "only for backwards compatibility. You should use the new standard container property `contents`",
360
- PendingDeprecationWarning,
360
+ "only for backwards compatibility. You should use the new standard container property `contents`."
361
+ "API will be removed in version 5.0.",
362
+ DeprecationWarning,
361
363
  stacklevel=2,
362
364
  )
363
365
  focus_position = self.focus_position
@@ -468,17 +470,6 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
468
470
  return
469
471
  raise ValueError(f"Widget not found in Pile contents: {item!r}")
470
472
 
471
- def _get_focus(self) -> Widget:
472
- warnings.warn(
473
- f"method `{self.__class__.__name__}._get_focus` is deprecated, "
474
- f"please use `{self.__class__.__name__}.focus` property",
475
- DeprecationWarning,
476
- stacklevel=3,
477
- )
478
- if not self.contents:
479
- return None
480
- return self.contents[self.focus_position][0]
481
-
482
473
  def get_focus(self) -> Widget | None:
483
474
  """
484
475
  Return the widget in focus, for backwards compatibility. You may
@@ -487,8 +478,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
487
478
  """
488
479
  warnings.warn(
489
480
  "for backwards compatibility."
490
- "You may also use the new standard container property .focus to get the child widget in focus.",
491
- PendingDeprecationWarning,
481
+ "You may also use the new standard container property .focus to get the child widget in focus."
482
+ "API will be removed in version 5.0.",
483
+ DeprecationWarning,
492
484
  stacklevel=2,
493
485
  )
494
486
  if not self.contents:
@@ -498,8 +490,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
498
490
  def set_focus(self, item: Widget | int) -> None:
499
491
  warnings.warn(
500
492
  "for backwards compatibility."
501
- "You may also use the new standard container property .focus to get the child widget in focus.",
502
- PendingDeprecationWarning,
493
+ "You may also use the new standard container property .focus to get the child widget in focus."
494
+ "API will be removed in version 5.0.",
495
+ DeprecationWarning,
503
496
  stacklevel=2,
504
497
  )
505
498
  if isinstance(item, int):
@@ -516,7 +509,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
516
509
  warnings.warn(
517
510
  "only for backwards compatibility."
518
511
  "You should use the new standard container properties "
519
- "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
512
+ "`focus` and `focus_position` to get the child widget in focus or modify the focus position."
513
+ "API will be removed in version 4.0.",
520
514
  DeprecationWarning,
521
515
  stacklevel=2,
522
516
  )
@@ -527,7 +521,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
527
521
  warnings.warn(
528
522
  "only for backwards compatibility."
529
523
  "You should use the new standard container properties "
530
- "`focus` and `focus_position` to get the child widget in focus or modify the focus position.",
524
+ "`focus` and `focus_position` to get the child widget in focus or modify the focus position."
525
+ "API will be removed in version 4.0.",
531
526
  DeprecationWarning,
532
527
  stacklevel=2,
533
528
  )
@@ -557,36 +552,6 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
557
552
  raise IndexError(f"No Pile child widget at position {position}").with_traceback(exc.__traceback__) from exc
558
553
  self.contents.focus = position
559
554
 
560
- def _get_focus_position(self) -> int | None:
561
- warnings.warn(
562
- f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
563
- f"please use `{self.__class__.__name__}.focus_position` property",
564
- DeprecationWarning,
565
- stacklevel=3,
566
- )
567
- if not self.contents:
568
- raise IndexError("No focus_position, Pile is empty")
569
- return self.contents.focus
570
-
571
- def _set_focus_position(self, position: int) -> None:
572
- """
573
- Set the widget in focus.
574
-
575
- position -- index of child widget to be made focus
576
- """
577
- warnings.warn(
578
- f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
579
- f"please use `{self.__class__.__name__}.focus_position` property",
580
- DeprecationWarning,
581
- stacklevel=3,
582
- )
583
- try:
584
- if position < 0 or position >= len(self.contents):
585
- raise IndexError(f"No Pile child widget at position {position}")
586
- except TypeError as exc:
587
- raise IndexError(f"No Pile child widget at position {position}").with_traceback(exc.__traceback__) from exc
588
- self.contents.focus = position
589
-
590
555
  def get_pref_col(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> int | None:
591
556
  """Return the preferred column for the cursor, or None."""
592
557
  if not self.selectable():
@@ -609,6 +574,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
609
574
  """
610
575
  Return a size appropriate for passing to self.contents[i][0].render
611
576
  """
577
+ warnings.warn(
578
+ "get_item_size is going to be deprecated and can be removed soon."
579
+ "This method is not used by the urwid codebase and `get_rows_sizes` is used for the similar purposes.",
580
+ PendingDeprecationWarning,
581
+ stacklevel=2,
582
+ )
612
583
  _w, (f, height) = self.contents[i]
613
584
  if f == WHSettings.PACK:
614
585
  if not size:
@@ -637,6 +608,10 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
637
608
  self,
638
609
  focus: bool = False,
639
610
  ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int] | tuple[()]]]:
611
+ """Get rows widths, heights and render size parameters
612
+
613
+ Fixed case expect widget sizes calculation with several cycles for unknown height cases.
614
+ """
640
615
  if not self.contents:
641
616
  return (), (), ()
642
617
 
@@ -728,6 +703,55 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
728
703
  tuple(w_h_args[idx] for idx in range(len(w_h_args))),
729
704
  )
730
705
 
706
+ def _get_flow_rows_sizes(
707
+ self,
708
+ size: tuple[int],
709
+ focus: bool = False,
710
+ ) -> tuple[Sequence[int], Sequence[int], Sequence[tuple[int] | tuple[()]]]:
711
+ """Get rows widths, heights and render size parameters
712
+
713
+ Flow case is the simplest one: minimum cycles in the logic and no widgets manipulation.
714
+ Here we can make some shortcuts
715
+ """
716
+ maxcol = size[0]
717
+ if not self.contents:
718
+ return (maxcol,), (0,), ()
719
+
720
+ widths: Sequence[int] = (maxcol,) * len(self.contents)
721
+ heights: list[int] = []
722
+ w_h_args: list[tuple[int, int] | tuple[int] | tuple[()]] = []
723
+ focus_position = self.focus_position
724
+
725
+ for i, (w, (f, height)) in enumerate(self.contents):
726
+ if isinstance(w, Widget):
727
+ w_sizing = w.sizing()
728
+ else:
729
+ warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
730
+ w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))
731
+
732
+ focused = focus and i == focus_position
733
+
734
+ if f == WHSettings.GIVEN:
735
+ heights.append(height)
736
+ w_h_args.append((maxcol, height))
737
+ elif Sizing.FLOW in w_sizing:
738
+ heights.append(w.rows((maxcol,), focus=focused))
739
+ w_h_args.append((maxcol,))
740
+ elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
741
+ heights.append(w.pack((), focused)[1])
742
+ w_h_args.append(())
743
+ else:
744
+ warnings.warn(
745
+ f"Unusual widget {i} sizing {w_sizing} for {f.upper()}). "
746
+ f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for height calculation",
747
+ PileWarning,
748
+ stacklevel=3,
749
+ )
750
+ heights.append(w.rows((maxcol,), focus=focused))
751
+ w_h_args.append((maxcol,))
752
+
753
+ return (widths, tuple(heights), tuple(w_h_args))
754
+
731
755
  def get_rows_sizes(
732
756
  self,
733
757
  size: tuple[int, int] | tuple[int] | tuple[()],
@@ -736,13 +760,20 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
736
760
  """Get rows widths, heights and render size parameters"""
737
761
  if not size:
738
762
  return self._get_fixed_rows_sizes(focus=focus)
763
+ if len(size) == 1:
764
+ return self._get_flow_rows_sizes(size, focus=focus)
739
765
 
740
- maxcol = size[0]
741
- item_rows = None
766
+ maxcol, maxrow = size
767
+ if not self.contents:
768
+ return (maxcol,), (maxrow,), ()
742
769
 
743
- widths: list[int] = []
744
- heights: list[int] = []
745
- w_h_args: list[tuple[int, int] | tuple[int] | tuple[()]] = []
770
+ remaining: int = maxrow
771
+
772
+ widths: Sequence[int] = (maxcol,) * len(self.contents)
773
+ heights: dict[int, int] = {}
774
+ weighted: dict[int, int] = {}
775
+ w_h_args: dict[int, tuple[int, int] | tuple[int] | tuple[()]] = {}
776
+ focus_position = self.focus_position
746
777
 
747
778
  for i, (w, (f, height)) in enumerate(self.contents):
748
779
  if isinstance(w, Widget):
@@ -751,12 +782,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
751
782
  warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
752
783
  w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))
753
784
 
754
- item_focus = focus and self.focus == w
755
- widths.append(maxcol)
785
+ focused = focus and i == focus_position
756
786
 
757
787
  if f == WHSettings.GIVEN:
758
- heights.append(height)
759
- w_h_args.append((maxcol, height))
788
+ heights[i] = height
789
+ w_h_args[i] = (maxcol, height)
790
+ remaining -= height
760
791
  elif f == WHSettings.PACK or len(size) == 1:
761
792
  if Sizing.FLOW in w_sizing:
762
793
  w_h_arg: tuple[int] | tuple[()] = (maxcol,)
@@ -771,16 +802,72 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
771
802
  )
772
803
  w_h_arg = (maxcol,)
773
804
 
774
- heights.append(w.pack(w_h_arg, item_focus)[1])
775
- w_h_args.append(w_h_arg)
805
+ item_height = w.pack(w_h_arg, focused)[1]
806
+ heights[i] = item_height
807
+ w_h_args[i] = w_h_arg
808
+ remaining -= item_height
809
+ elif height:
810
+ weighted[i] = height
776
811
  else:
777
- if item_rows is None:
778
- item_rows = self.get_item_rows(size, focus)
779
- rows = item_rows[i]
780
- heights.append(rows)
781
- w_h_args.append((maxcol, rows))
812
+ heights[i] = 0
813
+ w_h_args[i] = (maxcol, 0) # zero-weighted items treated as ('given', 0)
814
+
815
+ sum_weight = sum(weighted.values())
816
+ if remaining <= 0 and focus_position in weighted:
817
+ # We need to hide some widgets: focused part will be not displayed by default
818
+ # At this place we have to operate also negative "remaining" to be sure that widget really fit
819
+ before = []
820
+ after = []
821
+ for idx, height in heights.items():
822
+ if height:
823
+ if idx < focus_position:
824
+ before.append(idx)
825
+ else:
826
+ after.append(idx)
827
+
828
+ # Try to move to the center
829
+ offset = len(before) - len(after)
830
+ if not offset:
831
+ indexes = (element for pair in zip(after[::-1], before) for element in pair)
832
+ elif offset > 0:
833
+ indexes = (
834
+ *before[:offset],
835
+ *(element for pair in zip(after[::-1], before[offset:]) for element in pair),
836
+ )
837
+ else:
838
+ indexes = (
839
+ *after[-1 : offset - 1 : -1],
840
+ *(element for pair in zip(after[offset - 1 :: -1], before) for element in pair),
841
+ )
842
+
843
+ for idx in indexes:
844
+ height, heights[idx] = heights[idx], 0
845
+ remaining += height
846
+ if remaining > 0:
847
+ break
848
+
849
+ remaining = max(remaining, 0)
850
+
851
+ for idx in sorted(weighted, key=lambda idx: (idx != focus_position, idx)):
852
+ # We have to sort weighted items way that focused widget will gain render priority
853
+ if remaining == 0:
854
+ rows = 0
855
+ else:
856
+ rows = int(float(remaining) * weighted[idx] / sum_weight + 0.5)
857
+ if not rows and idx == focus_position:
858
+ # Special case: we have to force as minimum 1 row for focused widget to prevent broken behaviour
859
+ rows = 1
860
+ remaining -= rows
861
+ sum_weight -= weighted[idx]
782
862
 
783
- return (tuple(widths), tuple(heights), tuple(w_h_args))
863
+ heights[idx] = rows
864
+ w_h_args[idx] = (maxcol, rows)
865
+
866
+ return (
867
+ widths,
868
+ tuple(heights[idx] for idx in range(len(heights))),
869
+ tuple(w_h_args[idx] for idx in range(len(w_h_args))),
870
+ )
784
871
 
785
872
  def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
786
873
  """Get packed sized for widget."""
@@ -789,74 +876,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
789
876
  widths, heights, _ = self.get_rows_sizes(size, focus)
790
877
  return (max(widths), sum(heights))
791
878
 
792
- def get_item_rows(self, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool) -> list[int]:
793
- """
794
- Return a list of the number of rows used by each widget in self.contents
795
- """
796
- remaining = None
797
- maxcol = size[0]
798
- if len(size) == 2:
799
- remaining = size[1]
800
-
801
- rows_numbers = []
879
+ def get_item_rows(self, size: tuple[int] | tuple[int, int], focus: bool) -> list[int]:
880
+ """A list of the number of rows used by each widget in self.contents.
802
881
 
803
- if remaining is None:
804
- # pile is a flow widget
805
- for i, (w, (f, height)) in enumerate(self.contents):
806
- if isinstance(w, Widget):
807
- w_sizing = w.sizing()
808
- else:
809
- warnings.warn(f"{w!r} is not a Widget", PileWarning, stacklevel=3)
810
- w_sizing = frozenset((Sizing.FLOW, Sizing.BOX))
811
-
812
- focused = focus and self.focus == w
813
-
814
- if f == WHSettings.GIVEN:
815
- rows_numbers.append(height)
816
- elif Sizing.FLOW in w_sizing:
817
- rows_numbers.append(w.rows((maxcol,), focus=focused))
818
- elif Sizing.FIXED in w_sizing and f == WHSettings.PACK:
819
- rows_numbers.append(w.pack((), focused)[0])
820
- else:
821
- warnings.warn(
822
- f"Unusual widget {i} sizing {w_sizing} for {f.upper()}). "
823
- f"Assuming wrong sizing and using {Sizing.FLOW.upper()} for height calculation",
824
- PileWarning,
825
- stacklevel=3,
826
- )
827
- rows_numbers.append(w.rows((maxcol,), focus=focused))
828
- return rows_numbers
829
-
830
- # pile is a box widget
831
- # do an extra pass to calculate rows for each widget
832
- wtotal = 0
833
- for w, (f, height) in self.contents:
834
- if f == WHSettings.PACK:
835
- rows = w.rows((maxcol,), focus=focus and self.focus == w)
836
- rows_numbers.append(rows)
837
- remaining -= rows
838
- elif f == WHSettings.GIVEN:
839
- rows_numbers.append(height)
840
- remaining -= height
841
- elif height:
842
- rows_numbers.append(None)
843
- wtotal += height
844
- else:
845
- rows_numbers.append(0) # zero-weighted items treated as ('given', 0)
846
-
847
- if wtotal == 0:
848
- raise PileError("No weighted widgets found for Pile treated as a box widget")
849
-
850
- remaining = max(remaining, 0)
851
-
852
- for i, (_w, (_f, height)) in enumerate(self.contents):
853
- li = rows_numbers[i]
854
- if li is None:
855
- rows = int(float(remaining) * height / wtotal + 0.5)
856
- rows_numbers[i] = rows
857
- remaining -= rows
858
- wtotal -= height
859
- return rows_numbers
882
+ This method is a normally used only by `get_item_size` for the BOX case..
883
+ """
884
+ return list(self.get_rows_sizes(size, focus)[1])
860
885
 
861
886
  def render(
862
887
  self,
@@ -876,7 +901,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
876
901
  combinelist.append((canv, i, item_focus))
877
902
 
878
903
  if not combinelist:
879
- return SolidCanvas(" ", size[0], (size[1:] + (0,))[0])
904
+ return SolidCanvas(" ", size[0], (*size[1:], 0)[0])
880
905
 
881
906
  out = CanvasCombine(combinelist)
882
907
  if len(size) == 2 and size[1] != out.rows():
@@ -894,18 +919,17 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
894
919
 
895
920
  i = self.focus_position
896
921
  _widths, heights, size_args = self.get_rows_sizes(size, focus=True)
897
- coords = self.focus.get_cursor_coords(size_args[i])
922
+ if (coords := self.focus.get_cursor_coords(size_args[i])) is not None:
923
+ x, y = coords
924
+ if i > 0:
925
+ y += sum(heights[:i])
898
926
 
899
- if coords is None:
900
- return None
901
- x, y = coords
902
- if i > 0:
903
- for r in heights[:i]:
904
- y += r
905
- return x, y
927
+ return x, y
906
928
 
907
- def rows(self, size: tuple[int] | tuple[int, int], focus: bool = False) -> int:
908
- return sum(self.get_item_rows(size, focus))
929
+ return None
930
+
931
+ def rows(self, size: tuple[int], focus: bool = False) -> int:
932
+ return sum(self.get_rows_sizes(size, focus)[1])
909
933
 
910
934
  def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
911
935
  """Pass the keypress to the widget in focus.
@@ -955,8 +979,7 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
955
979
  if not hasattr(self.focus, "get_pref_col"):
956
980
  return
957
981
 
958
- pref_col = self.focus.get_pref_col(w_size)
959
- if pref_col is not None:
982
+ if (pref_col := self.focus.get_pref_col(w_size)) is not None:
960
983
  self.pref_col = pref_col
961
984
 
962
985
  def move_cursor_to_coords(
@@ -982,10 +1005,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
982
1005
  if not w.selectable():
983
1006
  return False
984
1007
 
985
- if hasattr(w, "move_cursor_to_coords"):
986
- rval = w.move_cursor_to_coords(w_size, col, row - wrow)
987
- if rval is False:
988
- return False
1008
+ if hasattr(w, "move_cursor_to_coords") and w.move_cursor_to_coords(w_size, col, row - wrow) is False:
1009
+ return False
989
1010
 
990
1011
  self.focus_position = i
991
1012
  return True
urwid/widget/popup.py CHANGED
@@ -98,8 +98,8 @@ class PopUpTarget(WidgetDecoration[WrappedWidget]):
98
98
  def _update_overlay(self, size: tuple[int, int], focus: bool) -> None:
99
99
  canv = self._original_widget.render(size, focus=focus)
100
100
  self._cache_original_canvas = canv # imperfect performance hack
101
- pop_up = canv.get_pop_up()
102
- if pop_up:
101
+
102
+ if pop_up := canv.get_pop_up():
103
103
  left, top, (w, overlay_width, overlay_height) = pop_up
104
104
  if self._pop_up != w:
105
105
  self._pop_up = w
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import typing
4
- import warnings
5
4
 
6
5
  from .constants import BAR_SYMBOLS, Align, Sizing, WrapMode
7
6
  from .text import Text
@@ -95,15 +94,6 @@ class ProgressBar(Widget):
95
94
  self._done = done
96
95
  self._invalidate()
97
96
 
98
- def _set_done(self, done):
99
- warnings.warn(
100
- f"Method `{self.__class__.__name__}._set_done` is deprecated, "
101
- f"please use property `{self.__class__.__name__}.done`",
102
- DeprecationWarning,
103
- stacklevel=2,
104
- )
105
- self.done = done
106
-
107
97
  def rows(self, size: tuple[int], focus: bool = False) -> int:
108
98
  return 1
109
99
 
@@ -25,8 +25,6 @@ import contextlib
25
25
  import enum
26
26
  import typing
27
27
 
28
- from typing_extensions import Protocol, runtime_checkable
29
-
30
28
  from .constants import BOX_SYMBOLS, SHADE_SYMBOLS, Sizing
31
29
  from .widget_decoration import WidgetDecoration, WidgetError
32
30
 
@@ -84,8 +82,8 @@ class ScrollbarSymbols(str, enum.Enum):
84
82
  DRAWING_DOUBLE = BOX_SYMBOLS.DOUBLE.VERTICAL
85
83
 
86
84
 
87
- @runtime_checkable
88
- class WidgetProto(Protocol):
85
+ @typing.runtime_checkable
86
+ class WidgetProto(typing.Protocol):
89
87
  """Protocol for widget.
90
88
 
91
89
  Due to protocol cannot inherit non-protocol bases, define several obligatory Widget methods.
@@ -117,8 +115,8 @@ class WidgetProto(Protocol):
117
115
  def render(self, size: tuple[int, int], focus: bool = False) -> Canvas: ...
118
116
 
119
117
 
120
- @runtime_checkable
121
- class SupportsScroll(WidgetProto, Protocol):
118
+ @typing.runtime_checkable
119
+ class SupportsScroll(WidgetProto, typing.Protocol):
122
120
  """Scroll specific methods."""
123
121
 
124
122
  def get_scrollpos(self, size: tuple[int, int], focus: bool = False) -> int: ...
@@ -126,8 +124,8 @@ class SupportsScroll(WidgetProto, Protocol):
126
124
  def rows_max(self, size: tuple[int, int] | None = None, focus: bool = False) -> int: ...
127
125
 
128
126
 
129
- @runtime_checkable
130
- class SupportsRelativeScroll(WidgetProto, Protocol):
127
+ @typing.runtime_checkable
128
+ class SupportsRelativeScroll(WidgetProto, typing.Protocol):
131
129
  """Relative scroll-specific methods."""
132
130
 
133
131
  def require_relative_scroll(self, size: tuple[int, int], focus: bool = False) -> bool: ...
@@ -140,10 +138,7 @@ class SupportsRelativeScroll(WidgetProto, Protocol):
140
138
  def orig_iter(w: Widget) -> Iterator[Widget]:
141
139
  visited = {w}
142
140
  yield w
143
- while hasattr(w, "original_widget"):
144
- w = w.original_widget
145
- if w in visited:
146
- break
141
+ while (w := getattr(w, "original_widget", w)) not in visited:
147
142
  visited.add(w)
148
143
  yield w
149
144
 
@@ -194,8 +189,8 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
194
189
  first_visible = False
195
190
  for pwi, (w, _o) in enumerate(ow.contents):
196
191
  wcanv = w.render((maxcol,))
197
- wh = wcanv.rows()
198
- if wh:
192
+
193
+ if wh := wcanv.rows():
199
194
  ch += wh
200
195
 
201
196
  if not last_hidden and ch >= self._trim_top:
@@ -235,17 +230,13 @@ class Scrollable(WidgetDecoration[WrappedWidget]):
235
230
  canv = canvas.CompositeCanvas(canv_full)
236
231
  canv_cols, canv_rows = canv.cols(), canv.rows()
237
232
 
238
- if canv_cols <= maxcol:
239
- pad_width = maxcol - canv_cols
240
- if pad_width > 0:
241
- # Canvas is narrower than available horizontal space
242
- canv.pad_trim_left_right(0, pad_width)
233
+ if canv_cols <= maxcol and (pad_width := maxcol - canv_cols) > 0:
234
+ # Canvas is narrower than available horizontal space
235
+ canv.pad_trim_left_right(0, pad_width)
243
236
 
244
- if canv_rows <= maxrow:
245
- fill_height = maxrow - canv_rows
246
- if fill_height > 0:
247
- # Canvas is lower than available vertical space
248
- canv.pad_trim_top_bottom(0, fill_height)
237
+ if canv_rows <= maxrow and (fill_height := maxrow - canv_rows) > 0:
238
+ # Canvas is lower than available vertical space
239
+ canv.pad_trim_top_bottom(0, fill_height)
249
240
 
250
241
  if canv_cols <= maxcol and canv_rows <= maxrow:
251
242
  # Canvas is small enough to fit without trimming
@@ -552,17 +543,18 @@ class ScrollBar(WidgetDecoration[WrappedWidget]):
552
543
  use_relative = False
553
544
 
554
545
  if not use_relative:
555
- ow_rows_max = ow_base.rows_max(size, focus)
556
- if ow_rows_max <= maxrow:
546
+ if ow_base.rows_max(size, focus) > maxrow:
547
+ ow_canv = render_for_scrollbar()
548
+ # re-calculate using wrapped size
549
+ ow_rows_max = ow_base.rows_max(ow_size, focus)
550
+ pos = ow_base.get_scrollpos(ow_size, focus)
551
+ posmax = ow_rows_max - maxrow
552
+ thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))
553
+
554
+ else:
557
555
  # Canvas fits without scrolling - no scrollbar needed
558
556
  return render_no_scrollbar()
559
557
 
560
- ow_canv = render_for_scrollbar()
561
- ow_rows_max = ow_base.rows_max(ow_size, focus)
562
- pos = ow_base.get_scrollpos(ow_size, focus)
563
- posmax = ow_rows_max - maxrow
564
- thumb_weight = min(1.0, maxrow / max(1, ow_rows_max))
565
-
566
558
  # Thumb shrinks/grows according to the ratio of <number of visible lines> / <number of total lines>
567
559
  thumb_height = max(1, round(thumb_weight * maxrow)) # pylint: disable=possibly-used-before-assignment
568
560