diff --git a/archinstall/default_profiles/desktops/hyprland.py b/archinstall/default_profiles/desktops/hyprland.py index 155be617c..dc2f15591 100644 --- a/archinstall/default_profiles/desktops/hyprland.py +++ b/archinstall/default_profiles/desktops/hyprland.py @@ -65,7 +65,7 @@ def _ask_seat_access(self) -> None: group, header=header, allow_skip=False, - frame=FrameProperties.minimal(str(_('Seat access'))), + frame=FrameProperties.min(str(_('Seat access'))), alignment=Alignment.CENTER ).single() diff --git a/archinstall/default_profiles/desktops/sway.py b/archinstall/default_profiles/desktops/sway.py index b03bfb71f..e6e22a914 100644 --- a/archinstall/default_profiles/desktops/sway.py +++ b/archinstall/default_profiles/desktops/sway.py @@ -75,7 +75,7 @@ def _ask_seat_access(self) -> None: group, header=header, allow_skip=False, - frame=FrameProperties.minimal(str(_('Seat access'))), + frame=FrameProperties.min(str(_('Seat access'))), alignment=Alignment.CENTER ).single() diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index 20188b970..264d01daa 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -229,7 +229,7 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt allow_skip=True, allow_reset=True, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Encryption type'))) + frame=FrameProperties.min(str(_('Encryption type'))) ).single() match result.type_: diff --git a/archinstall/lib/disk/partitioning_menu.py b/archinstall/lib/disk/partitioning_menu.py index 5754e60cf..d5eeab82a 100644 --- a/archinstall/lib/disk/partitioning_menu.py +++ b/archinstall/lib/disk/partitioning_menu.py @@ -217,7 +217,7 @@ def _prompt_partition_fs_type(self, prompt: Optional[str] = None) -> FilesystemT group, header=prompt, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Filesystem'))), + frame=FrameProperties.min(str(_('Filesystem'))), allow_skip=False ).single() diff --git a/archinstall/lib/interactions/disk_conf.py b/archinstall/lib/interactions/disk_conf.py index e585e3c76..a4bb86d01 100644 --- a/archinstall/lib/interactions/disk_conf.py +++ b/archinstall/lib/interactions/disk_conf.py @@ -118,7 +118,7 @@ def select_disk_config( group, allow_skip=True, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Disk configuration type'))), + frame=FrameProperties.min(str(_('Disk configuration type'))), allow_reset=True ).single() @@ -186,7 +186,7 @@ def select_lvm_config( group, allow_reset=True, allow_skip=True, - frame=FrameProperties.minimal(str(_('LVM configuration type'))), + frame=FrameProperties.min(str(_('LVM configuration type'))), alignment=Alignment.CENTER ).single() @@ -236,7 +236,7 @@ def select_main_filesystem_format(advanced_options: bool = False) -> disk.Filesy result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal('Filesystem'), + frame=FrameProperties.min('Filesystem'), allow_skip=False ).single() diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index 73ce48723..8d1419137 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -79,7 +79,7 @@ def ask_for_a_timezone(preset: Optional[str] = None) -> Optional[str]: group, allow_reset=True, allow_skip=True, - frame=FrameProperties.minimal(str(_('Timezone'))), + frame=FrameProperties.min(str(_('Timezone'))), alignment=Alignment.CENTER, ).single() @@ -103,7 +103,7 @@ def ask_for_audio_selection(preset: Optional[AudioConfiguration] = None) -> Opti group, allow_skip=True, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Audio'))) + frame=FrameProperties.min(str(_('Audio'))) ).single() match result.type_: @@ -148,7 +148,7 @@ def select_archinstall_language(languages: List[Language], preset: Language) -> allow_skip=True, allow_reset=False, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(header=str(_('Select language'))) + frame=FrameProperties.min(header=str(_('Select language'))) ).single() match result.type_: @@ -270,7 +270,7 @@ def select_additional_repositories(preset: List[str]) -> List[str]: result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal('Additional repositories'), + frame=FrameProperties.min('Additional repositories'), allow_reset=True, allow_skip=True ).multi() diff --git a/archinstall/lib/interactions/network_menu.py b/archinstall/lib/interactions/network_menu.py index 9f183330b..217e990a0 100644 --- a/archinstall/lib/interactions/network_menu.py +++ b/archinstall/lib/interactions/network_menu.py @@ -59,7 +59,7 @@ def _select_iface(self, data: List[Nic]) -> Optional[str]: result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Interfaces'))), + frame=FrameProperties.min(str(_('Interfaces'))), allow_skip=True ).single() @@ -123,7 +123,7 @@ def _edit_iface(self, edit_nic: Nic) -> Nic: header=header, allow_skip=False, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Modes'))) + frame=FrameProperties.min(str(_('Modes'))) ).single() match result.type_: @@ -177,7 +177,7 @@ def ask_to_configure_network(preset: Optional[NetworkConfiguration]) -> Optional result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Network configuration'))), + frame=FrameProperties.min(str(_('Network configuration'))), allow_reset=True, allow_skip=True ).single() diff --git a/archinstall/lib/interactions/system_conf.py b/archinstall/lib/interactions/system_conf.py index 8759d26a4..4c465b6e6 100644 --- a/archinstall/lib/interactions/system_conf.py +++ b/archinstall/lib/interactions/system_conf.py @@ -37,7 +37,7 @@ def select_kernel(preset: List[str] = []) -> List[str]: allow_skip=True, allow_reset=True, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Kernel'))) + frame=FrameProperties.min(str(_('Kernel'))) ).multi() match result.type_: @@ -66,7 +66,7 @@ def ask_for_bootloader(preset: Optional[Bootloader]) -> Optional[Bootloader]: result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Bootloader'))), + frame=FrameProperties.min(str(_('Bootloader'))), allow_skip=True ).single() diff --git a/archinstall/lib/locale/locale_menu.py b/archinstall/lib/locale/locale_menu.py index b162f830e..61ac9d109 100644 --- a/archinstall/lib/locale/locale_menu.py +++ b/archinstall/lib/locale/locale_menu.py @@ -137,7 +137,7 @@ def select_locale_lang(preset: Optional[str] = None) -> Optional[str]: result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties(str(_('Locale language'))), + frame=FrameProperties.min(str(_('Locale language'))), allow_skip=True, ).single() @@ -161,7 +161,7 @@ def select_locale_enc(preset: Optional[str] = None) -> Optional[str]: result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties(str(_('Locale encoding'))), + frame=FrameProperties.min(str(_('Locale encoding'))), allow_skip=True, ).single() @@ -193,7 +193,7 @@ def select_kb_layout(preset: Optional[str] = None) -> Optional[str]: result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Keyboard layout'))), + frame=FrameProperties.min(str(_('Keyboard layout'))), allow_skip=True, ).single() diff --git a/archinstall/lib/mirrors.py b/archinstall/lib/mirrors.py index a3b5e928c..7f66e8845 100644 --- a/archinstall/lib/mirrors.py +++ b/archinstall/lib/mirrors.py @@ -322,7 +322,7 @@ def select_mirror_regions(preset: Dict[str, List[MirrorStatusEntryV3]]) -> Dict[ result = SelectMenu( group, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Mirror regions'))), + frame=FrameProperties.min(str(_('Mirror regions'))), allow_reset=True, allow_skip=True, ).multi() diff --git a/archinstall/lib/profile/profile_menu.py b/archinstall/lib/profile/profile_menu.py index 26fa32922..db5d5403c 100644 --- a/archinstall/lib/profile/profile_menu.py +++ b/archinstall/lib/profile/profile_menu.py @@ -180,7 +180,7 @@ def select_greeter( result = SelectMenu( group, allow_skip=True, - frame=FrameProperties.minimal(str(_('Greeter'))), + frame=FrameProperties.min(str(_('Greeter'))), alignment=Alignment.CENTER ).single() @@ -216,7 +216,7 @@ def select_profile( allow_reset=allow_reset, allow_skip=True, alignment=Alignment.CENTER, - frame=FrameProperties.minimal(str(_('Main profile'))) + frame=FrameProperties.min(str(_('Main profile'))) ).single() match result.type_: diff --git a/archinstall/tui/curses_menu.py b/archinstall/tui/curses_menu.py index 38ed19ece..a544ceb9d 100644 --- a/archinstall/tui/curses_menu.py +++ b/archinstall/tui/curses_menu.py @@ -1,4 +1,5 @@ import curses +import dataclasses import curses.panel import os import signal @@ -53,7 +54,7 @@ def _set_help_viewport(self) -> 'Viewport': height, x_start, int((max_height / 2) - height / 2), - frame=FrameProperties.minimal(str(_('Archinstall help'))) + frame=FrameProperties.min(str(_('Archinstall help'))) ) def _confirm_interrupt(self, screen: Any, warning: str) -> bool: @@ -113,7 +114,8 @@ def add_str(self, screen: Any, row: int, col: int, text: str, color: STYLE): try: screen.addstr(row, col, text, tui.get_color(color)) except curses.error: - debug(f'Curses error while adding string to viewport: {text}') + # debug(f'Curses error while adding string to viewport: {text}') + pass def add_frame( self, @@ -222,19 +224,20 @@ def _get_frame_dim( if frame.w_frame_style == FrameStyle.MIN: frame_start = min([e.col for e in entries]) - frame_end = max([(e.col + len(e.text)) for e in entries]) + max_row_cols = [(e.col + len(e.text) + 1) for e in entries] + max_row_cols.append(header_len) + frame_end = max(max_row_cols) + # 2 for frames, 1 for extra space start away from frame # must align with def _adjust_entries frame_end += 3 # 2 for frame - else: - frame_start = 0 - frame_end = max_width - if frame.h_frame_style == FrameStyle.MIN: frame_height = len(rows) + 1 if frame_height > max_height: frame_height = max_height else: + frame_start = 0 + frame_end = max_width frame_height = max_height - 1 return _FrameDim(frame_start, frame_end, frame_height) @@ -276,6 +279,7 @@ def _assemble_entries(self, entries: List[ViewportEntry]) -> str: view[e.row] = self._replace_str(view[e.row], e.col, e.text) view = [v.rstrip() for v in view] + return '\n'.join(view) @@ -383,8 +387,18 @@ def gather(self) -> Optional[str]: @dataclass class ViewportState: - displayed_rows: List[ViewportEntry] - percentage: Optional[int] + cur_pos: int + displayed_entries: List[ViewportEntry] + scroll_pct: Optional[int] + + def offset(self) -> int: + return min([entry.row for entry in self.displayed_entries], default=0) + + def get_rows(self) -> list[int]: + rows = set() + for entry in self.displayed_entries: + rows.add(entry.row) + return list(rows) @dataclass @@ -428,32 +442,31 @@ def update( cur_pos: int = 0, scroll_pos: Optional[int] = None ): - self._state = self._find_visible_rows(lines, cur_pos, scroll_pos) + self._state = self._get_viewport_state(lines, cur_pos, scroll_pos) + visible_entries = self._adjust_entries_row(self._state.displayed_entries) if self._frame: - lines = self.add_frame( - self._state.displayed_rows, + visible_entries = self.add_frame( + visible_entries, self.width, self.height, frame=self._frame, - scroll_pct=self._state.percentage + scroll_pct=self._state.scroll_pct ) - else: - lines = self._state.displayed_rows x_offset = 0 if self._alignment == Alignment.CENTER: - x_offset = self.align_center(lines, self.width) + x_offset = self.align_center(visible_entries, self.width) self._main_win.erase() - for line in lines: + for entry in visible_entries: self.add_str( self._main_win, - line.row, - line.col + x_offset, - line.text, - line.style + entry.row, + entry.col + x_offset, + entry.text, + entry.style ) self._main_win.refresh() @@ -477,36 +490,54 @@ def _calc_scroll_percent( return percentage - def _find_visible_rows( + def _get_viewport_state( self, entries: List[ViewportEntry], cur_pos: int, scroll_pos: Optional[int] = 0 ) -> ViewportState: if not entries: - return ViewportState([], 0) + return ViewportState(cur_pos, [], 0) + # we will be checking if the cursor pos is in the same window + # of rows as the previous selection, in that case we can keep + # the currently shows entries to prevent weird moving in long lists if self._state is not None: - if cur_pos > 0: - try: - a=1/0 - except Exception: - import traceback - debug(traceback.format_exc()) - debug(cur_pos) - - prev_rows = [e.row for e in self._state.displayed_rows] - - # if cur_pos in prev_rows: - # return self._state - else: - debug("NOOOOOOOOOOOOOOOOONE") - - row_values = [e.row for e in entries] + rows = self._state.get_rows() + + if cur_pos in rows: + same_row_entries = [entry for entry in entries if entry.row in rows] + return ViewportState( + cur_pos, + same_row_entries, + self._state.scroll_pct + ) - total_rows = max(row_values) + 1 # rows start with 0 and we need the count + total_rows = max([e.row for e in entries]) + 1 # rows start with 0 so add 1 for the count screen_rows = self._get_available_screen_rows() + visible_entries = self._get_visible_entries( + entries, + cur_pos, + screen_rows, + scroll_pos, + total_rows + ) + + if scroll_pos is not None: + percentage = self._calc_scroll_percent(total_rows, screen_rows, scroll_pos) + else: + percentage = None + return ViewportState(cur_pos, visible_entries, percentage) + + def _get_visible_entries( + self, + entries: List[ViewportEntry], + cur_pos: int, + screen_rows: int, + scroll_pos: Optional[int], + total_rows: int + ) -> list[ViewportEntry]: if scroll_pos is not None: if total_rows <= screen_rows: start = 0 @@ -518,25 +549,34 @@ def _find_visible_rows( if total_rows <= screen_rows: start = 0 end = total_rows - elif cur_pos < screen_rows: - start = 0 - end = screen_rows else: - start = cur_pos - screen_rows + 1 - end = cur_pos + 1 + if self._state is None: + if cur_pos < screen_rows: + start = 0 + end = screen_rows + else: + start = cur_pos - screen_rows + 1 + end = cur_pos + 1 + else: + if cur_pos < self._state.cur_pos: + start = cur_pos + end = cur_pos + screen_rows + else: + start = cur_pos - screen_rows + 1 + end = cur_pos + 1 - rows = [entry for entry in entries if start <= entry.row < end] - smallest = min([e.row for e in rows]) + return [entry for entry in entries if start <= entry.row < end] - for entry in rows: - entry.row = entry.row - smallest + def _adjust_entries_row(self, entries: List[ViewportEntry]) -> List[ViewportEntry]: + assert self._state is not None + modified = [] - if scroll_pos is not None: - percentage = self._calc_scroll_percent(total_rows, screen_rows, scroll_pos) - else: - percentage = None + for entry in entries: + mod = dataclasses.replace(entry) + mod.row = entry.row - self._state.offset() + modified.append(mod) - return ViewportState(rows, percentage) + return modified def _replace_str(self, text: str, index: int = 0, replacement: str = '') -> str: len_replace = len(replacement) diff --git a/archinstall/tui/types.py b/archinstall/tui/types.py index 483f55448..1ee72edd6 100644 --- a/archinstall/tui/types.py +++ b/archinstall/tui/types.py @@ -89,7 +89,7 @@ def max(cls, header: str) -> 'FrameProperties': ) @classmethod - def minimal(cls, header: str) -> 'FrameProperties': + def min(cls, header: str) -> 'FrameProperties': return FrameProperties( header, FrameStyle.MIN,