0 | module CyBy.Draw.Internal.Navigation
 1 |
 2 | import Chem.Util
 3 | import CyBy.Draw.Internal.Atom
 4 | import CyBy.Draw.Internal.Graph
 5 | import CyBy.Draw.Internal.Role
 6 | import Data.Fin
 7 | import Data.Graph.Indexed
 8 | import Data.List
 9 | import Geom
10 |
11 | %default total
12 |
13 | public export
14 | data Direction = N | W | S | E
15 |
16 | dirAngle : Direction -> Angle
17 | dirAngle N = threeHalfPi
18 | dirAngle W = pi
19 | dirAngle S = halfPi
20 | dirAngle E = zero
21 |
22 | -- Returns a list of pairs with bond angles and the corresponding
23 | -- 'global' Fin k of all visible neighbours.
24 | bondAnglesWithNodes : CDIGraph k -> Fin k -> List (Angle, Fin k)
25 | bondAnglesWithNodes g x =
26 |   let p  := pointId (lab g x)
27 |       ns := visibleNeighbours g x
28 |   in mapMaybe (\fn => (,fn) <$> angle (pointId (lab g fn) - p)) ns
29 |
30 | -- Given an input angle and two nodes (with their positions and indices),
31 | -- returns the Fin k of the node that lies in the direction most closely
32 | -- matching the input angle.
33 | bestPointId : Direction -> (Point Id, Fin k) -> (Point Id, Fin k) -> Fin k
34 | bestPointId E (p1, i1) (p2, i2) = if p1.x >= p2.x then i1 else i2
35 | bestPointId S (p1, i1) (p2, i2) = if p1.y >= p2.y then i1 else i2
36 | bestPointId W (p1, i1) (p2, i2) = if p1.x <= p2.x then i1 else i2
37 | bestPointId N (p1, i1) (p2, i2) = if p1.y <= p2.y then i1 else i2
38 |
39 | -- An angular direction margin that is slightly smaller than half pi, in order
40 | -- to prevent the active node/edge to change perpendicular to the input angle.
41 | DirectionMargin : Angle
42 | DirectionMargin = Geom.Angle.angle (7 * pi / 16)
43 |
44 | -- Returns the angle of the line connecting two nodes, if possible,
45 | -- regardless of whether they are directly connected in the graph.
46 | angleEdge : CDIGraph k -> Fin k -> Fin k -> Maybe Angle
47 | angleEdge g n1 n2 =
48 |   let p1 := pointId (lab g n1)
49 |       p2 := pointId (lab g n2)
50 |    in angle (p2 - p1)
51 |
52 | ||| Given an input angle and a currently hovered node or edge,
53 | ||| attempts to transfer the role Hover to the most appropriate
54 | ||| neighboring node or edge based on angular proximity.
55 | ||| In the case of edge-to-node navigation, node positions are also
56 | ||| evaluated to determine the best match.
57 | ||| If no suitable candidate is found, the graph is returned unchanged.
58 | export
59 | moveActive : Direction -> CDGraph -> CDGraph
60 | moveActive d (G k g) =
61 |   G k $ case hoveredItem g of
62 |     N (i, _) =>
63 |       case minBy (minDelta (dirAngle d) . fst) (bondAnglesWithNodes g i) of
64 |         Just (b,j) =>
65 |           if minDelta b (dirAngle d) < DirectionMargin
66 |              then updateEdge j i (set Hover) (updateNode i (unset Hover) g)
67 |              else g
68 |         Nothing => g
69 |     E (E x y _) =>
70 |       case angleEdge g x y of
71 |         Just angl =>
72 |           if minDelta (dirAngle d) angl <= DirectionMargin || 
73 |              minDelta (dirAngle d) (angl + pi)  <= DirectionMargin 
74 |             then
75 |               let g := updateEdge x y (unset Hover) g
76 |                   n := bestPointId d (pointAt g x, x) (pointAt g y, y)
77 |               in updateNode n (set Hover) g
78 |             else g
79 |         Nothing => g
80 |     None => g
81 |