Graph: pan with left or right, open with the other

This commit is contained in:
sarah committed 2026-10-09 09:51:38 +02:00
1 parent d97f82beb2
commit cc7d624a57
8 files changed
+50 -62

No files matched your search

+3 -2
View File
@@ -168,9 +168,10 @@ class AppConfig:
# in place (like Obsidian's live preview). Only affects Markdown.
MarkdownEditing: str = "Live"
# Graph view: directories as dots too (off: only files and the references
# between them), and the mouse button that pans ("Left", "Middle", "Right").
# between them), and the mouse button that pans ("Left" or "Right"; the
# other one opens a dot).
GraphShowDirectories: bool = True
GraphPanButton: str = "Middle"
GraphPanButton: str = "Right"
# Double-click on a file: True opens it in SaraView's own viewer, False
# hands it to the system's default program. The menu always offers the other.
OpenFilesInApp: bool = True
+16 -31
View File
@@ -5,8 +5,8 @@ colours by depth, files White), joined to its parent directory and to every file
On a server the graph comes from /api/graph; locally BuildLocalGraph makes
the same one with the same reference rules as Backend/Graph.py. The layout is
the web view's force simulation (same constants), in numpy, drawn as it
settles. Scroll to zoom, pan with the button picked in Settings > Graph,
hover a dot to light up its links, click it to open it."""
settles. Scroll to zoom, hover a dot to light up its links; one mouse button
pans and the other opens a dot (Settings > Graph)."""
import math
import re
@@ -238,9 +238,8 @@ def FilterGraph(Graph: dict, ShowDirectories: bool) -> dict:
class GraphView(ft.Container):
"""Pan and zoom are done here rather than with an InteractiveViewer, so
the pan button can be picked (Settings > Graph). Flet reports drags for
the left and right buttons only, so Middle works like middle-click
autoscroll: click it to grab the graph, move to pan, click again to let go."""
the buttons can be picked (Settings > Graph): one mouse button pans, the
other opens the dot it clicks."""
def __init__(self, TheApp: App, Root: str, DirectoryPath: str,
OnOpenNode: Callable[[dict], None], OnClose: Callable[[], None]):
@@ -248,7 +247,9 @@ class GraphView(ft.Container):
self.Root = Root
self.DirectoryPath = DirectoryPath
self.OnOpenNode = OnOpenNode
self.PanButton = TheApp.Config.GraphPanButton
# Anything but "Left" (including an old "Middle") pans with Right.
self.PanButton = "Left" if TheApp.Config.GraphPanButton == "Left" else "Right"
self.OpenButton = "Right" if self.PanButton == "Left" else "Left"
self.ShowDirectories = TheApp.Config.GraphShowDirectories
self.Layout: Optional[Layout] = None
self.ScopePath = ""
@@ -264,7 +265,6 @@ class GraphView(ft.Container):
self.PanX = 0.0
self.PanY = 0.0
self.DragFrom: Optional[tuple[float, float]] = None
self.Grabbed = False # Middle: grabbed by a middle click
self.ScopePicker = ft.Dropdown(
value="Root",
@@ -288,21 +288,21 @@ class GraphView(ft.Container):
# Hidden (not just its text) once the graph is up: even empty, it sits
# over the graph and would catch every click.
self.StatusBox = ft.Container(self.Status, alignment=ft.Alignment.CENTER)
Hint = {"Left": "Drag to pan", "Right": "Right-drag to pan",
"Middle": "Middle-click to grab and pan, again to let go"}.get(self.PanButton, "")
Hint = ("Drag to pan, right-click a dot to open it" if self.PanButton == "Left"
else "Right-drag to pan, click a dot to open it")
self.Surface = ft.GestureDetector(
content=ft.Container(self.Plane, expand=True, clip_behavior=ft.ClipBehavior.HARD_EDGE, bgcolor=Palette.Base),
on_tap_up=self._OnTap,
on_tap_up=lambda e: self._OnClick(e, "Left"),
on_secondary_tap_up=lambda e: self._OnClick(e, "Right"),
on_hover=self._OnHover,
on_exit=lambda e: self._SetHovered(None),
on_scroll=self._OnScroll,
on_pan_start=lambda e: self._StartDrag(e, "Left"),
on_pan_down=lambda e: self._StartDrag(e, "Left"), # down, not start: start comes after the drag slop
on_pan_update=lambda e: self._Drag(e, "Left"),
on_pan_end=lambda e: self._EndDrag(),
on_right_pan_start=lambda e: self._StartDrag(e, "Right"),
on_right_pan_update=lambda e: self._Drag(e, "Right"),
on_right_pan_end=lambda e: self._EndDrag(),
on_tertiary_tap_up=self._OnMiddleClick,
hover_interval=20,
drag_interval=16,
expand=True,
@@ -317,7 +317,7 @@ class GraphView(ft.Container):
], spacing=12),
ft.Divider(height=1, color=Palette.Surface1),
ft.Stack([self.Surface, self.StatusBox], expand=True),
ft.Text(f"{Hint}, scroll to zoom, click a dot to open it.", size=11, color=Palette.Overlay0),
ft.Text(f"{Hint}, scroll to zoom.", size=11, color=Palette.Overlay0),
], spacing=8, expand=True),
expand=True, bgcolor=Palette.Base, padding=12,
)
@@ -488,29 +488,15 @@ class GraphView(ft.Container):
def _OnHover(self, Event) -> None:
X, Y = Event.local_position.x, Event.local_position.y
if self.Grabbed and self.DragFrom is not None:
self._PanBy(X - self.DragFrom[0], Y - self.DragFrom[1])
self.DragFrom = (X, Y)
return
self._SetHovered(self._NodeAt(X, Y))
def _OnTap(self, Event) -> None:
if self.Grabbed: # a left click also lets go
self.Grabbed = False
self.DragFrom = None
def _OnClick(self, Event, Button: str) -> None:
if Button != self.OpenButton:
return
Number = self._NodeAt(Event.local_position.x, Event.local_position.y)
if Number is not None:
self.OnOpenNode(self.Layout.Nodes[Number])
def _OnMiddleClick(self, Event) -> None:
if self.PanButton != "Middle":
return
self.Grabbed = not self.Grabbed
self.DragFrom = (Event.local_position.x, Event.local_position.y) if self.Grabbed else None
self.Surface.mouse_cursor = ft.MouseCursor.MOVE if self.Grabbed else ft.MouseCursor.BASIC
self.Surface.update()
def _StartDrag(self, Event, Button: str) -> None:
if Button == self.PanButton:
self.DragFrom = (Event.local_position.x, Event.local_position.y)
@@ -523,8 +509,7 @@ class GraphView(ft.Container):
self.DragFrom = (X, Y)
def _EndDrag(self) -> None:
if not self.Grabbed:
self.DragFrom = None
self.DragFrom = None
def _OnScroll(self, Event) -> None:
"""Zooms around the pointer."""
+4 -6
View File
@@ -122,11 +122,10 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego
value=Config.GraphShowDirectories)
PanButtonPicker = ft.RadioGroup(
ft.Column([
ft.Radio(value="Left", label="Left"),
ft.Radio(value="Middle", label="Middle (click to grab, move, click again to let go)"),
ft.Radio(value="Right", label="Right"),
ft.Radio(value="Right", label="Right (left-click opens a dot)"),
ft.Radio(value="Left", label="Left (right-click opens a dot)"),
], spacing=0),
value=Config.GraphPanButton,
value="Left" if Config.GraphPanButton == "Left" else "Right",
)
# Connection.
@@ -224,8 +223,7 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego
ft.Divider(height=1, color=Palette.Surface1),
ft.Text("Pan with mouse button"),
PanButtonPicker,
_Note("Scroll zooms. Flet can't follow a held middle button, so Middle works like "
"middle-click autoscroll."),
_Note("Scroll zooms. The other button opens the dot you click."),
], spacing=12, scroll=ft.ScrollMode.AUTO),
"Connection": ft.Column([
_Note("Browse a directory on this computer, or connect to a SaraView server."),