diff options
| author | Josh Rahm <rahm@google.com> | 2026-02-25 16:48:21 -0700 |
|---|---|---|
| committer | Josh Rahm <rahm@google.com> | 2026-02-25 16:50:19 -0700 |
| commit | 0b01da5b4c2c9de8e3f0b7454cc5cb177aba1dd4 (patch) | |
| tree | ded783c163b76e5fc41c9b8dfdda1c9417faef36 /src/Rahm | |
| parent | 983db9f0cd1d0e4e7e26f53554c6aa368fabef05 (diff) | |
| download | rde-0b01da5b4c2c9de8e3f0b7454cc5cb177aba1dd4.tar.gz rde-0b01da5b4c2c9de8e3f0b7454cc5cb177aba1dd4.tar.bz2 rde-0b01da5b4c2c9de8e3f0b7454cc5cb177aba1dd4.zip | |
[bugfix] use better algorithm to determine navigating screens in 2D.
Diffstat (limited to 'src/Rahm')
| -rw-r--r-- | src/Rahm/Desktop/Geometry.hs | 108 | ||||
| -rw-r--r-- | src/Rahm/Desktop/Keys/Wml.hs | 8 | ||||
| -rw-r--r-- | src/Rahm/Desktop/Workspaces.hs | 100 |
3 files changed, 153 insertions, 63 deletions
diff --git a/src/Rahm/Desktop/Geometry.hs b/src/Rahm/Desktop/Geometry.hs new file mode 100644 index 0000000..a0e8c92 --- /dev/null +++ b/src/Rahm/Desktop/Geometry.hs @@ -0,0 +1,108 @@ +module Rahm.Desktop.Geometry where + +import Data.List (minimumBy) +import Data.Maybe (listToMaybe) +import Data.Ord (comparing) +import Debug.Trace (trace) +import Text.Printf (printf) + +data GeomRectangle = GeomRectangle + { rectX, rectY, rectWidth, rectHeight :: Double + } + deriving (Show, Eq) + +data Direction = N | S | E | W + deriving (Show, Eq, Enum, Bounded) + +center :: GeomRectangle -> (Double, Double) +center r = (rectX r + rectWidth r / 2, rectY r + rectHeight r / 2) + +-- Distance from reference center to the closest point on candidate in the given direction +-- Assumes candidate is wholly in the given direction (verified elsewhere) +distanceToEdgeInDirection :: Direction -> GeomRectangle -> GeomRectangle -> Double +distanceToEdgeInDirection dir ref cand = + let (rx, ry) = center ref + (cx, cy) = center cand + left = rectX cand + right = rectX cand + rectWidth cand + top = rectY cand + bottom = rectY cand + rectHeight cand + in case dir of + N -> -- candidate is above: closest point is on bottom edge + let dx = if rx < left then left - rx else if rx > right then right - rx else 0 + dy = top - ry + in sqrt (dx*dx + dy*dy) + S -> -- candidate is below: closest point is on top edge + let dx = if rx < left then left - rx else if rx > right then right - rx else 0 + dy = ry - bottom + in sqrt (dx*dx + dy*dy) + W -> -- candidate is to the left: closest point is on right edge + let dy = if ry < top then top - ry else if ry > bottom then bottom - ry else 0 + dx = left - rx + in sqrt (dx*dx + dy*dy) + E -> -- candidate is to the right: closest point is on left edge + let dy = if ry < top then top - ry else if ry > bottom then bottom - ry else 0 + dx = rx - right + in sqrt (dx*dx + dy*dy) + + +boundingBox :: [GeomRectangle] -> (Double, Double, Double, Double) +boundingBox rs = + let xs = map rectX rs + ys = map rectY rs + xMaxes = zipWith (+) (map rectX rs) (map rectWidth rs) + yMaxes = zipWith (+) (map rectY rs) (map rectHeight rs) + in (minimum xs, minimum ys, maximum xMaxes - minimum xs, maximum yMaxes - minimum ys) + +translate :: GeomRectangle -> Double -> Double -> GeomRectangle +translate r dx dy = + GeomRectangle (rectX r + dx) (rectY r + dy) (rectWidth r) (rectHeight r) + +-- "Wholly in direction": candidate is completely in direction, with no overlap +whollyInDirection :: Direction -> GeomRectangle -> GeomRectangle -> Bool +whollyInDirection N r1 r2 = bottomEdge r1 <= topEdge r2 +whollyInDirection S r1 r2 = topEdge r1 >= bottomEdge r2 +whollyInDirection E r1 r2 = leftEdge r1 >= rightEdge r2 +whollyInDirection W r1 r2 = rightEdge r1 <= leftEdge r2 + +topEdge, bottomEdge, leftEdge, rightEdge :: GeomRectangle -> Double +topEdge r = rectY r +bottomEdge r = rectY r + rectHeight r +leftEdge r = rectX r +rightEdge r = rectX r + rectWidth r + +-- Generate up to 2 tile copies: original + one tile in direction +tileCandidates :: Direction -> Double -> Double -> GeomRectangle -> [GeomRectangle] +tileCandidates dir sw sh r = + case dir of + N -> [r, translate r 0 (-sh)] + S -> [r, translate r 0 sh] + W -> [r, translate r (-sw) 0] + E -> [r, translate r sw 0] + +closestInDirection :: + (Eq a) => + [(GeomRectangle, a)] -> + a -> + Direction -> + Maybe (GeomRectangle, a) +closestInDirection rects tag dir = do + ref <- listToMaybe [r | (r, t) <- rects, t == tag] + let allRects = map fst rects + (_, _, sw, sh) = boundingBox allRects + sw' = if sw == 0 then 1 else sw + sh' = if sh == 0 then 1 else sh + + let candidates = + [ (r, t, distanceToEdgeInDirection dir ref virt) + | (r, t) <- rects, + t /= tag, + virt <- tileCandidates dir sw' sh' r, + whollyInDirection dir virt ref + ] + + case candidates of + [] -> Nothing + _ -> Just (r, t) + where + (r, t, _) = minimumBy (comparing (\(_, _, d) -> d)) candidates diff --git a/src/Rahm/Desktop/Keys/Wml.hs b/src/Rahm/Desktop/Keys/Wml.hs index e5c45e7..9122ccb 100644 --- a/src/Rahm/Desktop/Keys/Wml.hs +++ b/src/Rahm/Desktop/Keys/Wml.hs @@ -539,22 +539,22 @@ readNextWorkspace = -- Workspace to the next screen to the right of the next workspace. (_, _, ",") -> do ws <- readNextWorkspaceName - liftXToFeed $ justWorkspace <$> getWorkspaceToTheRight ws + hoistMaybeT $ justWorkspace <$> getWorkspaceToTheRight ws -- Workspace on the screen below the current workspace.. (_, _, "%") -> do ws <- readNextWorkspaceName - liftXToFeed $ justWorkspace <$> getWorkspaceAbove ws + hoistMaybeT $ justWorkspace <$> getWorkspaceAbove ws -- Workspace on the screen above the current workspace.. (_, _, "+") -> do ws <- readNextWorkspaceName - liftXToFeed $ justWorkspace <$> getWorkspaceAbove ws + hoistMaybeT $ justWorkspace <$> getWorkspaceBelow ws -- Workspace to the next screen to the left of the next workspace. (_, _, ";") -> do ws <- readNextWorkspaceName - liftXToFeed $ justWorkspace <$> getWorkspaceToTheLeft ws + hoistMaybeT $ justWorkspace <$> getWorkspaceToTheLeft ws -- The workspace with the searched for window. (_, _, "/") -> diff --git a/src/Rahm/Desktop/Workspaces.hs b/src/Rahm/Desktop/Workspaces.hs index 48ed523..1bd5c51 100644 --- a/src/Rahm/Desktop/Workspaces.hs +++ b/src/Rahm/Desktop/Workspaces.hs @@ -17,7 +17,6 @@ module Rahm.Desktop.Workspaces workspaceWithWindow, getScreensOnSamePlane, getScreensOnDifferentPlane, - getWorkspacesAlongLine, getWorkspaceToTheRight, getWorkspaceToTheLeft, getWorkspaceAbove, @@ -35,7 +34,7 @@ import Data.List (find, sort, sortBy, sortOn, (\\)) import Data.List.Safe ((!!)) import Data.Maybe (fromMaybe, listToMaybe, mapMaybe) import Debug.Trace -import qualified Geometry +import Rahm.Desktop.Geometry import Rahm.Desktop.Common (getCurrentWorkspace, gotoWorkspace, runMaybeT_) import Rahm.Desktop.Logger import qualified Rahm.Desktop.StackSet as W @@ -51,6 +50,7 @@ import XMonad windows, withWindowSet, ) +import XMonad.Util.Loggers (logFileCount) import Prelude hiding ((!!)) newtype Selector = Selector (forall a. (a -> Bool) -> [a] -> Maybe a) @@ -135,7 +135,7 @@ getScreensOnSamePlane ss = where yCenter | (SD (Rectangle _ y _ h)) <- W.screenDetail . W.current $ ss = - (y + fromIntegral h) `div` 2 + (y + fromIntegral h) `div` 2 matchesYCenter (_, W.screenDetail -> (SD (Rectangle _ y _ h))) = y < yCenter && y + fromIntegral h > yCenter @@ -151,7 +151,7 @@ getScreensOnDifferentPlane ss = y + (fromIntegral h `div` 2) matchesScreen yCenter | (SD (Rectangle _ y _ h)) <- W.screenDetail (W.current ss) = - yCenter < y + fromIntegral h && yCenter > y + yCenter < y + fromIntegral h && yCenter > y accompanyingWorkspace :: WorkspaceId -> WorkspaceId accompanyingWorkspace [s] @@ -224,65 +224,47 @@ workspaceWithWindow wid = withWindowSet $ \(W.StackSet c v h _) -> (\(W.Workspace _ _ stack) -> wid `elem` W.integrate' stack) (map W.workspace (c : v) ++ h) -getWorkspacesAlongLine :: Geometry.Line Int32 -> X [WorkspaceId] -getWorkspacesAlongLine line = withWindowSet $ \windowSet -> - let extractRect :: ScreenDetail -> Geometry.GeometryRectangle Int32 - extractRect (SD (Rectangle x y w h)) = Geometry.rectangleToGeometry (Rectangle x y w h) - convertRect :: Geometry.GeometryRectangle Int32 -> WorkspaceId -> (Geometry.GeometryRectangle Double, WorkspaceId) - convertRect (Geometry.GeometryRectangle x y w h) tag = - ( Geometry.GeometryRectangle - { Geometry.geoX = fromIntegral x, - Geometry.geoY = fromIntegral y, - Geometry.geoWidth = fromIntegral w, - Geometry.geoHeight = fromIntegral h - }, - tag - ) - currentRect = extractRect . W.screenDetail . W.current $ windowSet - currentTag = W.tag $ W.workspace $ W.current windowSet - currentWs = convertRect currentRect currentTag - visibleRects = - map - ( \screen -> - let rect = extractRect (W.screenDetail screen) - tag = W.tag $ W.workspace screen - in convertRect rect tag - ) - (W.visible windowSet) - rects = currentWs : visibleRects - intersecting = Geometry.getIntersectingRectangles line' rects - line' = - Geometry.Line - { Geometry.x = fromIntegral (Geometry.x line), - Geometry.y = fromIntegral (Geometry.y line), - Geometry.dx = fromIntegral (Geometry.dx line), - Geometry.dy = fromIntegral (Geometry.dy line) - } - in return $ map Geometry.tag intersecting +getScreenRectangles :: X [(GeomRectangle, WorkspaceId)] +getScreenRectangles = + withWindowSet $ \(W.StackSet cur vis _ _) -> do + return $ ofScreen cur : map ofScreen vis + where + ofScreen (W.Screen (W.tag -> t) _ (SD rect)) = (toGeom rect, t) + toGeom (Rectangle x y w h) = + GeomRectangle + (fromIntegral x) + (fromIntegral y) + (fromIntegral w) + (fromIntegral h) -getWorkspaceToTheRight :: WorkspaceId -> X WorkspaceId -getWorkspaceToTheRight w = getWorkspaceAlong w (1, 0) False +getScreenRect :: WorkspaceId -> X (Maybe GeomRectangle) +getScreenRect w = do + rects <- getScreenRectangles + return $ listToMaybe [r | (r, w') <- rects, w' == w] -getWorkspaceToTheLeft :: WorkspaceId -> X WorkspaceId -getWorkspaceToTheLeft w = getWorkspaceAlong w (1, 0) True +getWorkspaceToTheRight :: WorkspaceId -> MaybeT X WorkspaceId +getWorkspaceToTheRight w = do + refRect <- lift $ getScreenRect w + rects <- lift getScreenRectangles + MaybeT $ return (snd <$> closestInDirection rects w E) -getWorkspaceAbove :: WorkspaceId -> X WorkspaceId -getWorkspaceAbove w = getWorkspaceAlong w (0, 1) True +getWorkspaceToTheLeft :: WorkspaceId -> MaybeT X WorkspaceId +getWorkspaceToTheLeft w = do + refRect <- lift $ getScreenRect w + rects <- lift getScreenRectangles + MaybeT $ return (snd <$> closestInDirection rects w W) -getWorkspaceBelow :: WorkspaceId -> X WorkspaceId -getWorkspaceBelow w = getWorkspaceAlong w (0, 1) False +getWorkspaceAbove :: WorkspaceId -> MaybeT X WorkspaceId +getWorkspaceAbove w = do + refRect <- lift $ getScreenRect w + rects <- lift getScreenRectangles + MaybeT $ return (snd <$> closestInDirection rects w N) -getWorkspaceAlong :: WorkspaceId -> (Int32, Int32) -> Bool -> X WorkspaceId -getWorkspaceAlong w dir reverseResults = do - center <- getWorkspaceCenter w - case center of - Nothing -> return w - Just (cx, cy) -> do - let p1 = (cx, cy) - p2 = (cx + fst dir, cy + snd dir) - let line = Geometry.lineThrough2Points p1 p2 - wss <- getWorkspacesAlongLine line - return $ if null wss then w else lookupNext w (if reverseResults then reverse wss else wss) +getWorkspaceBelow :: WorkspaceId -> MaybeT X WorkspaceId +getWorkspaceBelow w = do + refRect <- lift $ getScreenRect w + rects <- lift getScreenRectangles + MaybeT $ return (snd <$> closestInDirection rects w S) getWorkspaceCenter :: WorkspaceId -> X (Maybe (Int32, Int32)) getWorkspaceCenter w = withWindowSet $ \(W.StackSet cur vis _ _) -> @@ -297,7 +279,7 @@ getWorkspaceCenter w = withWindowSet $ \(W.StackSet cur vis _ _) -> wsTag = W.tag (W.workspace screen) in ((x + fromIntegral w `div` 2, y + fromIntegral h `div` 2), wsTag) -lookupNext :: Eq a => a -> [a] -> a +lookupNext :: (Eq a) => a -> [a] -> a lookupNext x [] = x lookupNext target (x : xs) | x == target = if null xs then target else head xs |