1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
|
{-# LANGUAGE PatternGuards #-}
module Graphics.Gloss.Data.ViewState
( Command (..)
, CommandConfig
, defaultCommandConfig
, ViewState (..)
, viewStateInit
, viewStateInitWithConfig
, updateViewStateWithEvent
, updateViewStateWithEventMaybe)
where
import Graphics.Gloss.Data.Vector
import Graphics.Gloss.Data.ViewPort
import Graphics.Gloss.Geometry.Angle
import Graphics.Gloss.Internals.Interface.Backend
import Graphics.Gloss.Internals.Interface.Event
import qualified Data.Map as Map
import Data.Map (Map)
import Data.Maybe
import Control.Monad (mplus)
import qualified Graphics.Gloss.Data.Point.Arithmetic as Pt
-- | The commands suported by the view controller.
data Command
= CRestore
| CTranslate
| CRotate
| CScale
-- bump zoom
| CBumpZoomOut
| CBumpZoomIn
-- bump translate
| CBumpLeft
| CBumpRight
| CBumpUp
| CBumpDown
-- bump rotate
| CBumpClockwise
| CBumpCClockwise
deriving (Show, Eq, Ord)
type CommandConfig = [(Command, [(Key, Maybe Modifiers)])]
-- | The default commands. Left click pans, wheel zooms, right click
-- rotates, "r" key resets.
defaultCommandConfig :: CommandConfig
defaultCommandConfig
= [ (CRestore,
[ (Char 'r', Nothing) ])
, (CTranslate,
[ ( MouseButton LeftButton
, Just (Modifiers { shift = Up, ctrl = Up, alt = Up }))
])
, (CScale,
[ ( MouseButton LeftButton
, Just (Modifiers { shift = Up, ctrl = Down, alt = Up }))
, ( MouseButton RightButton
, Just (Modifiers { shift = Up, ctrl = Up, alt = Up }))
])
, (CRotate,
[ ( MouseButton LeftButton
, Just (Modifiers { shift = Up, ctrl = Up, alt = Down }))
, ( MouseButton RightButton
, Just (Modifiers { shift = Up, ctrl = Down, alt = Up }))
])
-- bump zoom
, (CBumpZoomOut,
[ (MouseButton WheelDown, Nothing)
, (SpecialKey KeyPageDown, Nothing) ])
, (CBumpZoomIn,
[ (MouseButton WheelUp, Nothing)
, (SpecialKey KeyPageUp, Nothing)] )
-- bump translate
, (CBumpLeft,
[ (SpecialKey KeyLeft, Nothing) ])
, (CBumpRight,
[ (SpecialKey KeyRight, Nothing) ])
, (CBumpUp,
[ (SpecialKey KeyUp, Nothing) ])
, (CBumpDown,
[ (SpecialKey KeyDown, Nothing) ])
-- bump rotate
, (CBumpClockwise,
[ (SpecialKey KeyHome, Nothing) ])
, (CBumpCClockwise,
[ (SpecialKey KeyEnd, Nothing) ])
]
-- | Check if the provided key combination is some gloss viewport command.
isCommand
:: Map Command [(Key, Maybe Modifiers)]
-> Command -> Key -> Modifiers -> Bool
isCommand commands c key keyMods
| Just csMatch <- Map.lookup c commands
= or $ map (isCommand2 c key keyMods) csMatch
| otherwise
= False
-- | Check if the provided key combination is some gloss viewport command.
isCommand2 :: Command -> Key -> Modifiers -> (Key, Maybe Modifiers) -> Bool
isCommand2 _ key keyMods cMatch
| (keyC, mModsC) <- cMatch
, keyC == key
, case mModsC of
Nothing -> True
Just modsC -> modsC == keyMods
= True
| otherwise
= False
-- ViewControl State -----------------------------------------------------------
-- | State for controlling the viewport.
-- These are used by the viewport control component.
data ViewState
= ViewState {
-- | The command list for the viewport controller.
-- These can be safely overwridden at any time by deleting
-- or adding entries to the list.
-- Entries at the front of the list take precedence.
viewStateCommands :: !(Map Command [(Key, Maybe Modifiers)])
-- | How much to scale the world by for each step of the mouse wheel.
, viewStateScaleStep :: !Float
-- | How many degrees to rotate the world by for each pixel of x motion.
, viewStateRotateFactor :: !Float
-- | Ratio to scale the world by for each pixel of y motion.
, viewStateScaleFactor :: !Float
-- | During viewport translation,
-- where the mouse was clicked on the window to start the translate.
, viewStateTranslateMark :: !(Maybe (Float, Float))
-- | During viewport rotation,
-- where the mouse was clicked on the window to starte the rotate.
, viewStateRotateMark :: !(Maybe (Float, Float))
-- | During viewport scale,
-- where the mouse was clicked on the window to start the scale.
, viewStateScaleMark :: !(Maybe (Float, Float))
-- | The current viewport.
, viewStateViewPort :: ViewPort
}
-- | The initial view state.
viewStateInit :: ViewState
viewStateInit
= viewStateInitWithConfig defaultCommandConfig
-- | Initial view state, with user defined config.
viewStateInitWithConfig :: CommandConfig -> ViewState
viewStateInitWithConfig commandConfig
= ViewState
{ viewStateCommands = Map.fromList commandConfig
, viewStateScaleStep = 0.85
, viewStateRotateFactor = 0.6
, viewStateScaleFactor = 0.01
, viewStateTranslateMark = Nothing
, viewStateRotateMark = Nothing
, viewStateScaleMark = Nothing
, viewStateViewPort = viewPortInit }
-- | Apply an event to a `ViewState`.
updateViewStateWithEvent :: Event -> ViewState -> ViewState
updateViewStateWithEvent ev viewState
= fromMaybe viewState $ updateViewStateWithEventMaybe ev viewState
-- | Like 'updateViewStateWithEvent', but returns 'Nothing' if no update
-- was needed.
updateViewStateWithEventMaybe :: Event -> ViewState -> Maybe ViewState
updateViewStateWithEventMaybe (EventKey key keyState keyMods pos) viewState
| isCommand commands CRestore key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort = viewPortInit }
| isCommand commands CBumpZoomOut key keyMods
, keyState == Down
= Just $ controlZoomIn viewState
| isCommand commands CBumpZoomIn key keyMods
, keyState == Down
= Just $ controlZoomOut viewState
| isCommand commands CBumpLeft key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort = motionBump port (20, 0) }
| isCommand commands CBumpRight key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort = motionBump port (-20, 0) }
| isCommand commands CBumpUp key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort = motionBump port (0, -20) }
| isCommand commands CBumpDown key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort = motionBump port (0, 20) }
| isCommand commands CBumpClockwise key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort
= port { viewPortRotate = viewPortRotate port + 5 } }
| isCommand commands CBumpCClockwise key keyMods
, keyState == Down
= Just $ viewState { viewStateViewPort
= port { viewPortRotate = viewPortRotate port - 5 } }
-- Start Translation.
| isCommand commands CTranslate key keyMods
, keyState == Down
, not $ currentlyRotating || currentlyScaling
= Just $ viewState { viewStateTranslateMark = Just pos }
-- Start Rotation.
| isCommand commands CRotate key keyMods
, keyState == Down
, not $ currentlyTranslating || currentlyScaling
= Just $ viewState { viewStateRotateMark = Just pos }
-- Start Scale.
| isCommand commands CScale key keyMods
, keyState == Down
, not $ currentlyTranslating || currentlyRotating
= Just $ viewState { viewStateScaleMark = Just pos }
-- Kill current translate/rotate/scale command when the mouse button
-- is released.
| keyState == Up
= let killTranslate vs = vs { viewStateTranslateMark = Nothing }
killRotate vs = vs { viewStateRotateMark = Nothing }
killScale vs = vs { viewStateScaleMark = Nothing }
in Just
$ (if currentlyTranslating then killTranslate else id)
$ (if currentlyRotating then killRotate else id)
$ (if currentlyScaling then killScale else id)
$ viewState
| otherwise
= Nothing
where commands = viewStateCommands viewState
port = viewStateViewPort viewState
currentlyTranslating = isJust $ viewStateTranslateMark viewState
currentlyRotating = isJust $ viewStateRotateMark viewState
currentlyScaling = isJust $ viewStateScaleMark viewState
-- Note that only a translation or rotation applies, not both at the same time.
updateViewStateWithEventMaybe (EventMotion pos) viewState
= motionScale (viewStateScaleMark viewState) pos viewState `mplus`
motionTranslate (viewStateTranslateMark viewState) pos viewState `mplus`
motionRotate (viewStateRotateMark viewState) pos viewState
updateViewStateWithEventMaybe (EventResize _) _
= Nothing
-- | Zoom in a `ViewState` by the scale step.
controlZoomIn :: ViewState -> ViewState
controlZoomIn
viewState@ViewState
{ viewStateViewPort = port
, viewStateScaleStep = scaleStep }
= viewState
{ viewStateViewPort
= port { viewPortScale = viewPortScale port / scaleStep } }
-- | Zoom out a `ViewState` by the scale step.
controlZoomOut :: ViewState -> ViewState
controlZoomOut
viewState@ViewState
{ viewStateViewPort = port
, viewStateScaleStep = scaleStep }
= viewState
{ viewStateViewPort
= port { viewPortScale = viewPortScale port * scaleStep } }
-- | Offset a viewport.
motionBump :: ViewPort -> (Float, Float) -> ViewPort
motionBump
port@ViewPort
{ viewPortTranslate = trans
, viewPortScale = scale
, viewPortRotate = r }
(bumpX, bumpY)
= port { viewPortTranslate = trans Pt.- o }
where offset = (bumpX / scale, bumpY / scale)
o = rotateV (degToRad r) offset
-- | Apply a translation to the `ViewState`.
motionTranslate
:: Maybe (Float, Float) -- Location of first mark.
-> (Float, Float) -- Current position.
-> ViewState -> Maybe ViewState
motionTranslate Nothing _ _ = Nothing
motionTranslate (Just (markX, markY)) (posX, posY) viewState
= Just $ viewState
{ viewStateViewPort = port { viewPortTranslate = trans Pt.- o }
, viewStateTranslateMark = Just (posX, posY) }
where port = viewStateViewPort viewState
trans = viewPortTranslate port
scale = viewPortScale port
r = viewPortRotate port
dX = markX - posX
dY = markY - posY
offset = (dX / scale, dY / scale)
o = rotateV (degToRad r) offset
-- | Apply a rotation to the `ViewState`.
motionRotate
:: Maybe (Float, Float) -- Location of first mark.
-> (Float, Float) -- Current position.
-> ViewState -> Maybe ViewState
motionRotate Nothing _ _ = Nothing
motionRotate (Just (markX, _markY)) (posX, posY) viewState
= Just $ viewState
{ viewStateViewPort
= port { viewPortRotate = rotate - rotateFactor * (posX - markX) }
, viewStateRotateMark = Just (posX, posY) }
where port = viewStateViewPort viewState
rotate = viewPortRotate port
rotateFactor = viewStateRotateFactor viewState
-- | Apply a scale to the `ViewState`.
motionScale
:: Maybe (Float, Float) -- Location of first mark.
-> (Float, Float) -- Current position.
-> ViewState -> Maybe ViewState
motionScale Nothing _ _ = Nothing
motionScale (Just (_markX, markY)) (posX, posY) viewState
= Just $ viewState
{ viewStateViewPort
= let -- Limit the amount of downward scaling so it maxes
-- out at 1 percent of the original. There's not much
-- point scaling down to no pixels, or going negative
-- so that the image is inverted.
ss = if posY > markY
then scale - scale * (scaleFactor * (posY - markY))
else scale + scale * (scaleFactor * (markY - posY))
ss' = max 0.01 ss
in port { viewPortScale = ss' }
, viewStateScaleMark = Just (posX, posY) }
where port = viewStateViewPort viewState
scale = viewPortScale port
scaleFactor = viewStateScaleFactor viewState
|