Skip to content
Snippets Groups Projects
Select Git revision
  • 9ced463ca86103af692d6ef45f906f06a6d611ab
  • main default protected
  • blm_dev_chan
  • release/1.4.0 protected
  • widgets_draw
  • return_of_melodic_demo
  • task_cleanup
  • mixer2
  • dx/fb-save-restore
  • dx/dldldld
  • fpletz/flake
  • dx/jacksense-headset-mic-only
  • release/1.3.0 protected
  • fil3s-limit-filesize
  • allow-reloading-sunmenu
  • wifi-json-error-handling
  • app_text_viewer
  • shoegaze-fps
  • media_has_video_has_audio
  • fil3s-media
  • more-accurate-battery
  • v1.4.0
  • v1.3.0
  • v1.2.0
  • v1.2.0+rc1
  • v1.1.1
  • v1.1.0
  • v1.1.0+rc1
  • v1.0.0
  • v1.0.0+rc6
  • v1.0.0+rc5
  • v1.0.0+rc4
  • v1.0.0+rc3
  • v1.0.0+rc2
  • v1.0.0+rc1
35 results

browser.py

Blame
  • browser.py 7.23 KiB
    import os
    import uos
    import stat
    import math
    from st3m.goose import Callable, Generator, Optional
    from st3m.input import InputState
    from ctx import Context
    
    from .common.action_view import Action
    from .common.action_view import ActionView
    from .common import utils
    from .common import theme
    
    
    class Browser(ActionView):
        path: str
        navigate: Callable[[str], None]
        update_path: Callable[[str], None]
    
        dir_entries: list[tuple[str, str]] = []
        up_enabled: bool = False
        prev_enabled: bool = False
        next_enabled: bool = False
        delete_enabled: bool = True
        select_enabled: bool = True
        current_pos = 0
        current_entry: tuple[str, str]
    
        def __init__(
            self,
            path: str,
            navigate: Callable[[str], None],
            update_path: Callable[[str], None],
            selected: Optional[str] = None,
        ) -> None:
            super().__init__()
    
            self._delete_held_for = 0.0
            self._delete_hold_time = 1.5
            self._delete_require_release = False
    
            self._scroll_pos = 0.0
    
            self.path = path
            self.selected = selected
            self.navigate = navigate
            self.update_path = update_path
    
            self._update_actions()
            self._scan_path()
    
        def _on_action(self, index: int) -> None:
            if index == 4:
                if self.current_pos > 0:
                    self.current_pos -= 1
                    self._update_position()
            elif index == 3:
                self._up()
            elif index == 2:
                self._select()
            elif index == 1:
                if self.current_pos < len(self.dir_entries) - 1:
                    self.current_pos += 1
                    self._update_position()
            elif index == 0:
                self._delete()
    
        def draw(self, ctx: Context) -> None:
            utils.fill_screen(ctx, theme.BACKGROUND)
            super().draw(ctx)
    
            ctx.text_align = ctx.CENTER
            ctx.text_baseline = ctx.MIDDLE
            ctx.font_size = 40
            ctx.font = "Material Icons"
            ctx.rgb(*theme.BACKGROUND_TEXT)
            ctx.move_to(0, -5)
            ctx.text(self.current_entry[1])
    
            ctx.font_size = 24
            ctx.font = "Camp Font 3"
            xpos = 0.0
            if (width := ctx.text_width(self.current_entry[0])) > 220:
                xpos = math.sin(self._scroll_pos) * (width - 220) / 2
            ctx.move_to(xpos, 20)
            ctx.text(self.current_entry[0])
    
        def think(self, ins: InputState, delta_ms: int) -> None:
            super().think(ins, delta_ms)
    
            self._scroll_pos += delta_ms / 1000
    
            # Handle delete petal being held down
            if ins.captouch.petals[0].pressed:
                if not self._delete_require_release:
                    self._delete_held_for += delta_ms / 1000
                    if self._delete_held_for > self._delete_hold_time:
                        self._delete_held_for = self._delete_hold_time
                        self._delete_require_release = True
                        self._on_action(0)
            else:
                self._delete_held_for = 0.0
                self._delete_require_release = False
            self.actions[0].progress = self._delete_held_for / self._delete_hold_time
    
            for i in range(1, 5):
                if self.input.captouch.petals[i * 2].whole.pressed:
                    self._on_action(i)
                    return
    
        def _get_dir_entry(
            self, names: list[str]
        ) -> Generator[tuple[str, str], None, None]:
            for name in names:
                try:
                    if self.path + name == "/flash/sys/st3m":
                        yield (name, "\ue545")
                    elif utils.is_dir(self.path + name):
                        yield (name, "\ue2c7")
                    else:
                        yield (name, "\ue873")
                except Exception as e:
                    # This exception is why we're using a generator here
                    print(f"Failed to create entry for {name}: {e}")
    
        def _scan_path(self) -> None:
            dir = os.listdir(self.path)
    
            self.current_pos = 0
            if self.selected and self.selected in dir:
                self.current_pos = dir.index(self.selected)
    
            self.dir_entries = list(self._get_dir_entry(dir))
    
            self._update_position()
    
        def _change_path(self, path: str, selected: Optional[str] = None) -> None:
            self.path = path
            self.selected = selected
            self._scan_path()
            self.up_enabled = self.path != "/"
    
            up_action = self.actions[2]
            if up_action is not None:
                up_action.enabled = self.up_enabled
    
            self.update_path(self.path, selected)
            self._update_actions()
    
        def _select(self) -> None:
            if not self.select_enabled:
                return
    
            name = self.dir_entries[self.current_pos][0]
    
            old_path = self.path
            new_path = self.path + name
            try:
                if utils.is_dir(new_path):
                    self._change_path(new_path + "/")
                else:
                    self.update_path(self.path + name)
                    self.navigate("reader")
            except Exception as e:
                # TODO: Create error view
                print(f"Failed to open {new_path}: {e}")
                self._change_path(old_path)
    
        def _delete(self) -> None:
            if not self.delete_enabled:
                return
    
            name = self.dir_entries[self.current_pos][0]
            path = self.path + name
    
            try:
                if utils.is_dir(path):
                    utils.rmdirs(path)
                else:
                    os.remove(path)
                    print(f"deleted file: {path}")
    
                # refresh dir listing
                self.current_pos = max(self.current_pos - 1, 0)
                self._scan_path()
            except Exception as e:
                # TODO: Create error view
                print(f"Failed to delete {path}: {e}")
    
        def _up(self) -> None:
            if not self.up_enabled or len(self.path) <= 1:
                return
    
            segments = self.path[1:-1].split("/")
            if len(segments) == 1:
                self._change_path("/", segments[-1])
            else:
                selected = segments.pop()
                self._change_path("/" + "/".join(segments) + "/", selected)
    
        def _update_actions(self) -> None:
            self.actions = [
                Action(icon="\ue92b", label="Delete", enabled=self.delete_enabled),
                Action(icon="\ue409", label="Next", enabled=self.next_enabled),
                Action(icon="\ue876", label="Select", enabled=self.select_enabled),
                Action(icon="\ue5c4", label="Back", enabled=self.up_enabled),
                Action(icon="\ue5cb", label="Prev", enabled=self.prev_enabled),
            ]
    
        def _update_position(self) -> None:
            try:
                self.current_entry = self.dir_entries[self.current_pos]
                self.select_enabled = True
            except Exception:
                self.current_entry = ("\ue002", "No files")
                self.select_enabled = False
    
            self.up_enabled = self.path != "/"
            self.prev_enabled = self.current_pos > 0
            self.next_enabled = self.current_pos < len(self.dir_entries) - 1
            # disallow deleting st3m folder
            name = self.current_entry[0]
            self.delete_enabled = (
                self.path + name
                not in [
                    "/flash",
                    "/flash/sys",
                    "/flash/sys/st3m",
                    "/sd",
                ]
                and self.select_enabled
            )
            self._scroll_pos = math.pi / 2
    
            self._update_actions()