Threenpenny gui - захват координат мыши по щелчку и использование их для построения некоторого состояния - PullRequest
5 голосов
/ 07 января 2020

Чего бы я хотел достичь:

Каждый раз, когда пользователь нажимает на холст, берите координаты мыши из этого щелчка, чтобы создать Point x y и сохранить это состояние в [Point] так, чтобы в в более поздний момент времени, когда пользователь нажимает кнопку, я могу использовать [Point] в качестве ввода для некоторой функции.

Что я сделал:

Я определил Point данные типа, с конструктором с одним значением, например:

data Point = Point {
  x :: Int,
  y :: Int
} deriving (Show, Eq)

Я установил трехпенни UI (монада?) для определения пользовательского интерфейса, простые 400 x 400 canvas и button:

import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core
import Control.Monad

canvasSize = 400

setup :: Window -> UI ()
setup window = do
  return window # set title "Haskell GUI"

  canvas <- UI.canvas
    # set UI.height canvasSize
    # set UI.width canvasSize
    # set style [("border", "solid black 1px"), ("background", "#eee")]

  button <- UI.button #+ [string "Do stuff"]

  getBody window #+
    [
    column [element canvas],
    element canvas,
    element button
    ]

  on UI.mousedown canvas $ \(x, y) -> do
    -- Need to create a point x y and add it to a list here

  on UI.click button $ const $ do
    -- Need to get the list of points here

  return ()

А затем определили функцию для запуска пользовательского интерфейса в main:

runGui :: IO ()
runGui = startGUI defaultConfig setup

Итак, изначально я работал над точками рисования в том месте, где щелкнул пользователь. Я добился этого довольно легко, построив Point x y в аргументе lambda в mousedown и нарисовав его там. Я опускаю этот код, поскольку я решил это, и я не верю, что моя текущая проблема связана с этим (т.е. построение и рисование точки в области действия этой лямбды).

Вместо рисования, а затем выбрасывания Point привязанный к объему этой лямбды, я просто хотел бы сохранить эту точку в списке. Затем я хотел бы иметь возможность читать этот список, когда пользователь нажимает кнопку.

Я провел небольшое исследование Behaviour и Event (http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Reactive-Threepenny.html ) для стиля FRP, который, как я понимаю, является чем-то, что помогает создать что-то вроде шаблона редукса, но мой мозг начинает таять.

Основано на другом посте StackOverflow ( Mixing Threepenny- * 1051) * и StateT ) Я понял, что должен подключить события пользовательского интерфейса Threepenny для создания потока событий, затем использовать accumB для накопления каждого события из этого потока в поток некоторого поведения состояния, а затем преобразовать это Поведение состояния обратно в поток событий с применением и наблюдением в этом последнем потоке для обновления пользовательского интерфейса (я думаю, просто ... xD)

По крайней мере, это то, что я собрал, я проверил код в ответе на связанный вопрос StackOverflow, и он решил проблему, поставленную в этом конкретном вопросе. Но мне нужно зафиксировать положение мыши x y в потоке событий mousedown (который не был описан в этом фрагменте) и использовать его для создания потока Point, и вот где я застрял. Я пытался реализовать его, основываясь на изменении принятого кода ответов для своих целей, но столкнулся с множеством ошибок типа, потому что я явно неправильно понимаю, как части сочетаются друг с другом.

Вот моя попытка изменить код в принятом ответе на связанный вопрос StackOverflow:

-- This *should* be the bit that converts 
-- (x, y) click events to Point x y Event stream
let canvasClick = UI.mousedown canvas
    newPointStream = (\(x, y) -> Point x y) <$ (canvasClick)

-- This *should* be the bit that turns the 
-- Point x y Event stream into a "behaviour" stream
counter <- accumB (Point 0 0) newPointStream

Может ли кто-нибудь пролить свет? Я у стены: - (

1 Ответ

5 голосов
/ 08 января 2020

Одна из приятных особенностей threepenny-gui заключается в том, что у вас нет , чтобы использовать FRP, если вы этого не хотите. Простейшим подходом здесь, вероятно, было бы использование изменяемых ссылок из Data.IORef:

import Data.IORef

setup window = do
  -- ...

  pointsRef <- liftIO (newIORef [] :: IO (IORef [Point]))

  on UI.mousedown canvas $ \(x, y) -> do
    liftIO $ modifyIORef' pointsRef ((Point x y) :)

  on UI.click button $ const $ do
    points <- liftIO $ readIORef pointsRef
    -- use the list of points here

. Это создает изменяемый список точек pointsRef и инициализирует его как [], затем открывает новую точку на каждом mousedown. При нажатии кнопки читается список точек.

Другой подход заключается в использовании FRP. let pointEv = (uncurry Point) <$> UI.mousedown canvas дает вам Event Point. Тогда вы можете сделать let pointPrependEv = fmap (\p -> \list -> p : list) pointEv, чтобы дать Event ([Point] -> [Point]). Затем используйте pointsB <- accumB [] pointsPrependEv, чтобы получить Behavior [Point], в котором каждый раз хранится список точек. Наконец, используйте pointsB <@ UI.click button, чтобы получить Event [Point] для каждого нажатия кнопки. Теперь у вас есть событие для каждого нажатия кнопки, значением которого является список точек на данный момент, так что теперь вы можете запустить вычисление для этого события, используя register или любую другую функцию из threepenny-gui. Полная программа:

setup window = do
  -- ...

  let pointEv = (uncurry Point) <$> UI.mousedown canvas
      pointPrependEv = fmap (\p -> \list -> p : list) pointEv
  pointsB <- accumB [] pointPrependEv
  let buttonPressEv = pointsB <@ UI.click button
  -- use point list here via buttonPressEv

РЕДАКТИРОВАТЬ: Я только что заметил, что в своем вопросе вы уже поняли большинство из вышеупомянутых. Ваша единственная ошибка была при попытке сделать accumB [] newPointsStream. Если вы посмотрите на документацию , тип будет accumB :: MonadIO m => a -> Event (a -> a) -> m (Behavior a); обратите внимание, что для этого требуется Event (a -> a), а не простой Event a. Таким образом, исходное значение Event Point должно быть преобразовано в Event ([Point] -> [Point]) (которое для каждой новой точки возвращает функцию, добавляющую ее в список точек ввода), прежде чем ее можно будет использовать в accumB.

...