diff --git a/Backend/Settings.py b/Backend/Settings.py index 209d02d..8b6630c 100644 --- a/Backend/Settings.py +++ b/Backend/Settings.py @@ -49,9 +49,10 @@ DefaultSettings: dict[str, Any] = { # live preview). "Separate": rendered View and raw Edit are two modes. "MarkdownEditing": "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": True, - "GraphPanButton": "Middle", + "GraphPanButton": "Right", } DefaultThemesDirectory = ApplicationDirectory / "DefaultThemes" diff --git a/Client/Core.py b/Client/Core.py index 619c6a2..eeec24b 100644 --- a/Client/Core.py +++ b/Client/Core.py @@ -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 diff --git a/Client/Graph.py b/Client/Graph.py index 5707ee2..7736db6 100644 --- a/Client/Graph.py +++ b/Client/Graph.py @@ -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.""" diff --git a/Client/Settings.py b/Client/Settings.py index bff08e9..1286a20 100644 --- a/Client/Settings.py +++ b/Client/Settings.py @@ -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."), diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index 6225b92..bd61eb4 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -383,7 +383,7 @@ export default function App() { RootName={ActiveRoot} DirectoryPath={CurrentDirectoryPath} ShowDirectories={Settings ? Settings.GraphShowDirectories : true} - PanButton={Settings ? Settings.GraphPanButton : "Middle"} + PanButton={Settings ? Settings.GraphPanButton : "Right"} OnOpenNode={HandleOpenGraphNode} OnClose={() => SetIsGraphOpen(false)} /> diff --git a/Frontend/src/Components/GraphPane.jsx b/Frontend/src/Components/GraphPane.jsx index 1f20444..fae85b0 100644 --- a/Frontend/src/Components/GraphPane.jsx +++ b/Frontend/src/Components/GraphPane.jsx @@ -8,8 +8,9 @@ import { FetchGraph } from "../Api.js"; // it (Backend/Graph.py). With directories turned off (Settings > Graph) only // the files and the references between them are left. // The layout is a small force simulation: dots push each other apart and -// links pull them together until it settles. Pan with the button picked in -// Settings > Graph, scroll to zoom, drag a dot to move it, click one to open it. +// links pull them together until it settles. One mouse button pans (Settings +// > Graph), the other opens the dot it clicks or moves it when dragged. +// Scroll zooms. const ChildLength = 50; const ReferenceLength = 80; @@ -19,7 +20,6 @@ const LabelZoom = 0.7; // below this, only hovered labels are drawn const ClickSlop = 4; // pixels a click may wander and still count as a click // Directories by depth, like the headings: h1 Purple, h2 Blue, ... const DepthRoles = ["Purple", "Blue", "Green", "Yellow", "Orange", "Red"]; -const ButtonNumbers = { Left: 0, Middle: 1, Right: 2 }; function ReadRole(Name) { return getComputedStyle(document.documentElement).getPropertyValue(`--${Name}`).trim(); @@ -191,7 +191,9 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa const Depth = (Node.Id ? Node.Id.split("/").length : 0) - BaseDepth; Node.Colour = Node.IsDirectory ? DepthColours[Depth % DepthColours.length] : Colours.File; } - const PanNumber = ButtonNumbers[PanButton] ?? 1; + // Anything but "Left" (including an old "Middle") pans with Right. + const PanNumber = PanButton === "Left" ? 0 : 2; + const OpenNumber = PanNumber === 0 ? 2 : 0; const State = { Zoom: 1, @@ -313,11 +315,9 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa const Node = NodeAt(X, Y); State.Moved = false; State.DownAt = [X, Y]; - if (Event.button !== 0 || Event.button === PanNumber) { - Event.preventDefault(); // no middle-click autoscroll or right-click menu - } - if (Event.button === 0 && Node) { - // Left on a dot: a click opens it, a drag moves it. + Event.preventDefault(); + if (Event.button === OpenNumber && Node) { + // The open button on a dot: a click opens it, a drag moves it. State.Dragging = Node; Node.Pinned = true; } else if (Event.button === PanNumber) { @@ -365,7 +365,7 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa } function OnContextMenu(Event) { - if (PanNumber === 2) Event.preventDefault(); + Event.preventDefault(); // right-click pans or opens here, never the browser menu } function OnLeave() { @@ -432,8 +432,9 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa {!Graph && !Error &&
Building the graph…
} {Graph && }
- {PanButton === "Left" ? "Drag" : `${PanButton}-drag`} the background to pan, scroll to zoom, drag a dot to move it, - click one to open it. + {PanButton === "Left" + ? "Drag to pan, right-click a dot to open it (right-drag moves it), scroll to zoom." + : "Right-drag to pan, click a dot to open it (drag moves it), scroll to zoom."}
); diff --git a/Frontend/src/Components/SettingsPanel.jsx b/Frontend/src/Components/SettingsPanel.jsx index e9743ca..8393315 100644 --- a/Frontend/src/Components/SettingsPanel.jsx +++ b/Frontend/src/Components/SettingsPanel.jsx @@ -412,10 +412,12 @@ export default function SettingsPanel({
- UpdateField("GraphPanButton", Event.target.value)} + > + +
diff --git a/README.md b/README.md index c81a4e8..a2f8016 100644 --- a/README.md +++ b/README.md @@ -302,10 +302,10 @@ the current directory, as dots like Obsidian's graph: directories in the heading colours by depth (purple, blue, green, ...), files white, each joined to its parent directory and to the files that reference it (Markdown links and images, `[[wiki links]]` and portals). Hover a dot to light up its -links, click it to open it, scroll to zoom. Settings > Graph picks the mouse -button that pans (Middle by default) and can hide directories, leaving only -files and the links between them. In the client, Flet can't follow a held -middle button, so Middle there is click to grab, move, click to let go. +links, scroll to zoom. One mouse button pans and the other opens the dot +you click: right-drag pans and left-click opens by default, or the other way +round in Settings > Graph, which can also hide directories, leaving only +files and the links between them. Rename takes a `/` to move, like the web view (`../Archive/Notes.md`). Shortcuts: Enter opens, Backspace goes up, F2 renames, Delete deletes,