diff --git a/Backend/Settings.py b/Backend/Settings.py index 8b6630c..23f5122 100644 --- a/Backend/Settings.py +++ b/Backend/Settings.py @@ -53,6 +53,10 @@ DefaultSettings: dict[str, Any] = { # other one opens a dot). "GraphShowDirectories": True, "GraphPanButton": "Right", + # Directory dots: heading colours by depth (off: all Purple), and smaller + # the deeper they are. + "GraphColourByDepth": True, + "GraphShrinkByDepth": False, } DefaultThemesDirectory = ApplicationDirectory / "DefaultThemes" diff --git a/Client/Core.py b/Client/Core.py index eeec24b..185ce8c 100644 --- a/Client/Core.py +++ b/Client/Core.py @@ -172,6 +172,10 @@ class AppConfig: # other one opens a dot). GraphShowDirectories: bool = True GraphPanButton: str = "Right" + # Directory dots: heading colours by depth (off: all Mauve), and smaller + # the deeper they are. + GraphColourByDepth: bool = True + GraphShrinkByDepth: bool = False # 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 7736db6..2c57325 100644 --- a/Client/Graph.py +++ b/Client/Graph.py @@ -220,6 +220,8 @@ class Layout: # Directories go down the heading colours by depth (h1 Mauve, h2 Blue, ...), # starting again after Red. Files are White. DepthColours = ["Mauve", "Blue", "Green", "Yellow", "Peach", "Red"] +ShrinkPerDepth = 0.8 # with GraphShrinkByDepth, each level's directory dots are this much smaller +SmallestDirectory = 3 # ...but never smaller than a file's dot def DirectoryColour(Depth: int) -> str: @@ -251,6 +253,9 @@ class GraphView(ft.Container): 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.ColourByDepth = TheApp.Config.GraphColourByDepth + self.ShrinkByDepth = TheApp.Config.GraphShrinkByDepth + self.Depths = np.zeros(0) # each dot's depth below the graph's directory self.Layout: Optional[Layout] = None self.ScopePath = "" self.Size = (800.0, 600.0) @@ -276,7 +281,9 @@ class GraphView(ft.Container): ) self.CountText = ft.Text("", size=12, color=Palette.Overlay0) LegendItems: list[ft.Control] = [] - if self.ShowDirectories: + if self.ShowDirectories and not self.ColourByDepth: + LegendItems += [ft.Text("●", color=Palette.Mauve), ft.Text("Directory", size=12, color=Palette.Overlay0)] + elif self.ShowDirectories: LegendItems += [ft.Text("●", color=Palette.Mauve), ft.Text("●", color=Palette.Blue), ft.Text("●", color=Palette.Green), ft.Text("Directories by depth", size=12, color=Palette.Overlay0)] LegendItems += [ft.Text("●", color=Palette.White), ft.Text("File", size=12, color=Palette.Overlay0), @@ -365,8 +372,9 @@ class GraphView(ft.Container): return self.Layout = Layout(Graph) Base = len(Path.split("/")) if Path else 0 - self.Colours = [DirectoryColour((len(Node["Id"].split("/")) if Node["Id"] else 0) - Base) if Node["IsDirectory"] - else Palette.White for Node in self.Layout.Nodes] + self.Depths = np.array([(len(Node["Id"].split("/")) if Node["Id"] else 0) - Base for Node in self.Layout.Nodes]) + self.Colours = [(DirectoryColour(int(Depth)) if self.ColourByDepth else Palette.Mauve) if Node["IsDirectory"] + else Palette.White for Node, Depth in zip(self.Layout.Nodes, self.Depths)] self.StatusBox.visible = False self.StatusBox.update() # Settle in steps, drawing as it goes, like the web view's animation. @@ -393,7 +401,10 @@ class GraphView(ft.Container): Zoom = math.sqrt(Scale) IsDirectory = np.array([Node["IsDirectory"] for Node in self.Layout.Nodes]) Degree = np.sqrt(self.Layout.Degree) - self.Radius = np.maximum(2, np.where(IsDirectory, 5 + Degree * 1.6, 3 + Degree * 0.8) * Zoom) + DirectoryRadius = 5 + Degree * 1.6 + if self.ShrinkByDepth: + DirectoryRadius = np.maximum(SmallestDirectory, DirectoryRadius * ShrinkPerDepth ** self.Depths) + self.Radius = np.maximum(2, np.where(IsDirectory, DirectoryRadius, 3 + Degree * 0.8) * Zoom) def _Label(self, Number: int, Colour: str) -> cv.Text: X, Y = self.Screen[Number] diff --git a/Client/Settings.py b/Client/Settings.py index 1286a20..b497bb9 100644 --- a/Client/Settings.py +++ b/Client/Settings.py @@ -120,6 +120,10 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego # Graph. GraphDirectoriesSwitch = ft.Switch(label="Show directories (off: only files and the links between them)", value=Config.GraphShowDirectories) + ColourByDepthSwitch = ft.Switch(label="Colour directories by depth (off: all purple)", + value=Config.GraphColourByDepth) + ShrinkByDepthSwitch = ft.Switch(label="Smaller directory dots the deeper they are", + value=Config.GraphShrinkByDepth) PanButtonPicker = ft.RadioGroup( ft.Column([ ft.Radio(value="Right", label="Right (left-click opens a dot)"), @@ -217,9 +221,11 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego OwnAppearance, ], spacing=12, scroll=ft.ScrollMode.AUTO), "Graph": ft.Column([ - _Note("The graph view (Ctrl+G): directories in the heading colours by depth, files white, " + _Note("The graph view (Ctrl+G): directories purple or in the heading colours by depth, files white, " "joined to their directory and to the files that reference them."), GraphDirectoriesSwitch, + ColourByDepthSwitch, + ShrinkByDepthSwitch, ft.Divider(height=1, color=Palette.Surface1), ft.Text("Pan with mouse button"), PanButtonPicker, @@ -261,6 +267,8 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego Config.ShowFilesInTree = TreeFilesSwitch.value Config.MarkdownEditing = MarkdownEditingPicker.value or Config.MarkdownEditing Config.GraphShowDirectories = GraphDirectoriesSwitch.value + Config.GraphColourByDepth = ColourByDepthSwitch.value + Config.GraphShrinkByDepth = ShrinkByDepthSwitch.value Config.GraphPanButton = PanButtonPicker.value or Config.GraphPanButton Config.FontSize = _ReadInt(FontSizeField, Config.FontSize, 8, 32) Config.Mode = ModePicker.value or Config.Mode diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index bd61eb4..1ae57a4 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -384,6 +384,8 @@ export default function App() { DirectoryPath={CurrentDirectoryPath} ShowDirectories={Settings ? Settings.GraphShowDirectories : true} PanButton={Settings ? Settings.GraphPanButton : "Right"} + ColourByDepth={Settings ? Settings.GraphColourByDepth : true} + ShrinkByDepth={Settings ? Settings.GraphShrinkByDepth : false} OnOpenNode={HandleOpenGraphNode} OnClose={() => SetIsGraphOpen(false)} /> diff --git a/Frontend/src/Components/GraphPane.jsx b/Frontend/src/Components/GraphPane.jsx index fae85b0..b518c12 100644 --- a/Frontend/src/Components/GraphPane.jsx +++ b/Frontend/src/Components/GraphPane.jsx @@ -20,6 +20,8 @@ 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 ShrinkPerDepth = 0.8; // with ShrinkByDepth, each level's directory dots are this much smaller +const SmallestDirectory = 3; // ...but never smaller than a file's dot function ReadRole(Name) { return getComputedStyle(document.documentElement).getPropertyValue(`--${Name}`).trim(); @@ -132,7 +134,16 @@ function Tick(Nodes, Links, Alpha) { } } -export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, PanButton, OnOpenNode, OnClose }) { +export default function GraphPane({ + RootName, + DirectoryPath, + ShowDirectories, + PanButton, + ColourByDepth, + ShrinkByDepth, + OnOpenNode, + OnClose, +}) { const [Scope, SetScope] = useState("Root"); const [Graph, SetGraph] = useState(null); const [Error, SetError] = useState(null); @@ -189,7 +200,14 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa const BaseDepth = Graph.Path ? Graph.Path.split("/").length : 0; for (const Node of Nodes) { const Depth = (Node.Id ? Node.Id.split("/").length : 0) - BaseDepth; - Node.Colour = Node.IsDirectory ? DepthColours[Depth % DepthColours.length] : Colours.File; + if (!Node.IsDirectory) { + Node.Colour = Colours.File; + continue; + } + Node.Colour = ColourByDepth ? DepthColours[Depth % DepthColours.length] : Colours.Directory; + if (ShrinkByDepth) { + Node.Radius = Math.max(SmallestDirectory, Node.Radius * Math.pow(ShrinkPerDepth, Depth)); + } } // Anything but "Left" (including an old "Middle") pans with Right. const PanNumber = PanButton === "Left" ? 0 : 2; @@ -394,7 +412,7 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa Canvas.removeEventListener("contextmenu", OnContextMenu); Canvas.removeEventListener("wheel", OnWheel); }; - }, [Graph, ShowDirectories, PanButton]); + }, [Graph, ShowDirectories, PanButton, ColourByDepth, ShrinkByDepth]); const FileCount = Graph ? Graph.Nodes.filter((Node) => !Node.IsDirectory).length : 0; const DirectoryCount = Graph ? Graph.Nodes.length - FileCount : 0; @@ -410,7 +428,12 @@ export default function GraphPane({ RootName, DirectoryPath, ShowDirectories, Pa - {ShowDirectories && ( + {ShowDirectories && !ColourByDepth && ( + <> + ● Directory + + )} + {ShowDirectories && ColourByDepth && ( <> ● ● diff --git a/Frontend/src/Components/SettingsPanel.jsx b/Frontend/src/Components/SettingsPanel.jsx index 8393315..2fc5000 100644 --- a/Frontend/src/Components/SettingsPanel.jsx +++ b/Frontend/src/Components/SettingsPanel.jsx @@ -410,6 +410,20 @@ export default function SettingsPanel({ OnChange={(Event) => UpdateField("GraphShowDirectories", Event.target.checked)} /> +
+ + UpdateField("GraphColourByDepth", Event.target.checked)} + /> +
+
+ + UpdateField("GraphShrinkByDepth", Event.target.checked)} + /> +